PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
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
← All changes | stripe/controllers/FrmStrpLiteLinkController.php +143 -28 6.5.3 → trunk View file →
@@ -49,15 +49,16 @@
49 49 * @since 6.5, introduced in v3.0 of the Stripe add on.
50 50 *
51 51 * @param string $intent_id
52 52 * @param string $client_secret
53 + *
53 54 * @return void
54 55 */
55 56 private static function handle_one_time_stripe_link_return_url( $intent_id, $client_secret ) {
56 57 $redirect_helper = new FrmStrpLiteLinkRedirectHelper( $intent_id, $client_secret );
57 58 $frm_payment = new FrmTransLitePayment();
59 + $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
58 60
59 - $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
60 61 if ( ! $payment ) {
61 62 $redirect_helper->handle_error( 'no_payment_record' );
62 63 die();
63 64 }
@@ -62,8 +63,9 @@
62 63 die();
63 64 }
64 65
65 66 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_intent', $intent_id );
67 +
66 68 if ( ! is_object( $intent ) ) {
67 69 $redirect_helper->handle_error( 'intent_does_not_exist' );
68 70 die();
69 71 }
@@ -73,10 +75,11 @@
73 75 $redirect_helper->handle_error( 'unable_to_verify' );
74 76 die();
75 77 }
76 78
77 - $status = 'succeeded' === $intent->status ? 'complete' : 'authorized';
78 - $new_payment_values = compact( 'status' );
79 + $status = 'succeeded' === $intent->status ? 'complete' : 'authorized';
80 + $new_payment_values = (array) $payment;
81 + $new_payment_values['status'] = $status;
79 82
80 83 if ( 'complete' === $status ) {
81 84 $charge = reset( $intent->charges->data );
82 85 $new_payment_values['receipt_id'] = $charge->id;
@@ -92,13 +95,32 @@
92 95
93 96 $redirect_helper->set_entry_id( $entry->id );
94 97
95 98 $action = FrmStrpLiteActionsController::get_stripe_link_action( $entry->form_id );
99 +
96 100 if ( ! $action ) {
97 101 $redirect_helper->handle_error( 'no_stripe_link_action' );
98 102 die();
99 103 }
100 104
105 + $currency = FrmTransLiteAppHelper::get_action_setting( 'currency', array( 'payment' => $payment ) );
106 + $currency = FrmCurrencyHelper::get_currency( $currency );
107 + $actual_amount = intval( $intent->amount );
108 + $expected_amount = round( floatval( $payment->amount ), 2 );
109 +
110 + if ( 0 !== $currency['decimals'] ) {
111 + // Convert 10 to 1000 for example for Stripe.
112 + // But avoid for this a 0-decimal currency like JPY.
113 + $expected_amount *= 100;
114 + }
115 +
116 + $expected_amount = intval( round( $expected_amount ) );
117 +
118 + if ( $expected_amount !== $actual_amount ) {
119 + $redirect_helper->handle_error( 'amount_mismatch' );
120 + die();
121 + }
122 +
101 123 if ( 'succeeded' !== $intent->status ) {
102 124 if ( 'processing' === $intent->status ) {
103 125 FrmTransLitePaymentsController::change_payment_status( $payment, 'processing' );
104 126 $redirect_helper->handle_success( $entry, '' );
@@ -123,21 +145,45 @@
123 145 }
124 146
125 147 self::maybe_update_intent( $intent, $action, $entry );
126 148
127 - $frm_payment->update( $payment->id, $new_payment_values );
128 - FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
149 + // A webhook event may have already updated this payment, so check the status again before running triggers.
150 + $needs_triggers = $status !== $payment->status && self::payment_status_still_needs_to_update( $payment->id, $status );
151 + $updated = $frm_payment->update( $payment->id, $new_payment_values );
129 152
153 + if ( $needs_triggers && $updated ) {
154 + FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
155 + }
156 +
130 157 $redirect_helper->handle_success( $entry, isset( $charge ) ? $charge->id : '' );
131 158 die();
132 159 }
133 160
134 161 /**
162 + * Check that the payment status has not been updated by another request already.
163 + * This is to avoid running the payment actions twice.
164 + *
165 + * @since 6.35
166 + *
167 + * @param int $payment_id The id of the payment to check.
168 + * @param string $status The status the payment is about to be updated to.
169 + *
170 + * @return bool
171 + */
172 + private static function payment_status_still_needs_to_update( $payment_id, $status ) {
173 + $frm_payment = new FrmTransLitePayment();
174 + $payment = $frm_payment->get_one( $payment_id );
175 +
176 + return $payment && $payment->status !== $status;
177 + }
178 +
179 + /**
135 180 * Try to add the description to a Stripe link payment after it was confirmed.
136 181 *
137 182 * @param object $intent
138 - * @param WP_Post|stdClass $action
183 + * @param stdClass|WP_Post $action
139 184 * @param stdClass $entry
185 + *
140 186 * @return void
141 187 */
142 188 private static function maybe_update_intent( $intent, $action, $entry ) {
143 189 if ( empty( $action->post_content['description'] ) ) {
@@ -148,9 +194,9 @@
148 194 'entry' => $entry,
149 195 'form' => $entry->form_id,
150 196 'value' => $action->post_content['description'],
151 197 );
152 - $new_values = array( 'description' => FrmTransLiteAppHelper::process_shortcodes( $shortcode_atts ) );
198 + $new_values = array( 'description' => FrmTransLiteAppHelper::process_shortcodes( $shortcode_atts ) );
153 199 FrmStrpLiteAppHelper::call_stripe_helper_class( 'update_intent', $intent->id, $new_values );
154 200 }
155 201
156 202 /**
@@ -161,8 +207,9 @@
161 207 * @since 6.5, introduced in v3.0 of the Stripe add on.
162 208 *
163 209 * @param string $setup_id
164 210 * @param string $client_secret
211 + *
165 212 * @return void
166 213 */
167 214 private static function handle_recurring_stripe_link_return_url( $setup_id, $client_secret ) {
168 215 $redirect_helper = new FrmStrpLiteLinkRedirectHelper( $setup_id, $client_secret );
@@ -175,8 +222,9 @@
175 222 }
176 223
177 224 // Verify the setup intent.
178 225 $setup_intent = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_setup_intent', $setup_id );
226 +
179 227 if ( ! is_object( $setup_intent ) ) {
180 228 $redirect_helper->handle_error( 'intent_does_not_exist' );
181 229 die();
182 230 }
@@ -188,8 +236,9 @@
188 236 }
189 237
190 238 // Verify the entry.
191 239 $entry = FrmEntry::getOne( $payment->item_id );
240 +
192 241 if ( ! is_object( $entry ) ) {
193 242 $redirect_helper->handle_error( 'no_entry_found' );
194 243 die();
195 244 }
@@ -197,8 +246,9 @@
197 246 $redirect_helper->set_entry_id( $entry->id );
198 247
199 248 // Verify it's an action with Stripe link enabled.
200 249 $action = FrmStrpLiteActionsController::get_stripe_link_action( $entry->form_id );
250 +
201 251 if ( ! is_object( $action ) ) {
202 252 $redirect_helper->handle_error( 'no_stripe_link_action' );
203 253 die();
204 254 }
@@ -204,8 +254,9 @@
204 254 }
205 255
206 256 $customer_id = $setup_intent->customer;
207 257 $payment_method_id = self::get_link_payment_method( $setup_intent );
258 +
208 259 if ( ! $payment_method_id ) {
209 260 FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
210 261 $redirect_helper->handle_error( 'did_not_complete' );
211 262 die();
@@ -214,15 +265,15 @@
214 265 $amount = $payment->amount * 100;
215 266 $new_charge = array(
216 267 'customer' => $customer_id,
217 268 'default_payment_method' => $payment_method_id,
218 - 'plan' => FrmStrpLiteSubscriptionHelper::get_plan_from_atts(
269 + 'plan' => FrmStrpLiteSubscriptionHelper::get_plan_from_atts(
219 270 array(
220 271 'action' => $action,
221 272 'amount' => $amount,
222 273 )
223 274 ),
224 - 'expand' => array( 'latest_invoice.charge' ),
275 + 'expand' => array( 'latest_invoice.charge' ),
225 276 );
226 277
227 278 if ( ! FrmStrpLitePaymentTypeHandler::should_use_automatic_payment_methods( $action ) ) {
228 279 $new_charge['payment_settings'] = array(
@@ -235,8 +286,9 @@
235 286 'entry' => $entry,
236 287 );
237 288
238 289 $trial_end = FrmStrpLiteActionsController::get_trial_end_time( $atts );
290 +
239 291 if ( $trial_end ) {
240 292 $new_charge['trial_end'] = $trial_end;
241 293 }
242 294
@@ -248,8 +300,9 @@
248 300 die();
249 301 }
250 302
251 303 if ( 'succeeded' !== $setup_intent->status ) {
304 + FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
252 305 $redirect_helper->handle_error( 'payment_failed' );
253 306 die();
254 307 }
255 308
@@ -254,16 +307,27 @@
254 307 }
255 308
256 309 $customer_has_been_charged = ! empty( $subscription->latest_invoice->charge );
257 310 $atts['charge'] = FrmStrpLiteSubscriptionHelper::prepare_charge_object_for_subscription( $subscription, $amount );
258 - $new_payment_values = array();
311 + $new_payment_values = (array) $payment;
259 312
260 313 if ( $customer_has_been_charged ) {
261 314 $charge = $subscription->latest_invoice->charge;
262 315 $new_payment_values['receipt_id'] = $charge->id;
263 - $new_payment_values['status'] = 'pending' === $charge->status ? 'processing' : 'complete';
264 316
317 + if ( 'failed' === $charge->status ) {
318 + FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
319 +
320 + $new_payment_values['receipt_id'] = $charge->id;
321 + $frm_payment->update( $payment->id, $new_payment_values );
322 +
323 + $redirect_helper->handle_error( 'payment_failed', $charge->id );
324 + }
325 +
326 + $new_payment_values['status'] = 'pending' === $charge->status ? 'processing' : 'complete';
327 +
265 328 $new_payment_values['expire_date'] = '0000-00-00';
329 +
266 330 foreach ( $subscription->latest_invoice->lines->data as $line ) {
267 331 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $line->period->end );
268 332 }
269 333 } elseif ( $trial_end ) {
@@ -269,9 +333,9 @@
269 333 } elseif ( $trial_end ) {
270 334 $new_payment_values['amount'] = 0;
271 335 $new_payment_values['begin_date'] = gmdate( 'Y-m-d', time() );
272 336 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $trial_end );
273 - }
337 + }//end if
274 338
275 339 $new_payment_values['sub_id'] = FrmStrpLiteSubscriptionHelper::create_new_subscription( $atts );
276 340
277 341 $frm_payment->update( $payment->id, $new_payment_values );
@@ -282,8 +346,9 @@
282 346 FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
283 347
284 348 // Update the next billing date.
285 349 $next_bill_date = gmdate( 'Y-m-d' );
350 +
286 351 foreach ( $subscription->latest_invoice->lines->data as $line ) {
287 352 $next_bill_date = gmdate( 'Y-m-d', $line->period->end );
288 353 }
289 354
@@ -305,13 +370,15 @@
305 370 *
306 371 * @since 6.5, introduced in v3.0 of the Stripe add on.
307 372 *
308 373 * @param object $setup_intent
309 - * @return string|false
374 + *
375 + * @return false|string
310 376 */
311 377 private static function get_link_payment_method( $setup_intent ) {
312 378 if ( is_object( $setup_intent->latest_attempt ) && ! empty( $setup_intent->latest_attempt->payment_method_details ) ) {
313 379 $payment_method_details = $setup_intent->latest_attempt->payment_method_details;
380 +
314 381 foreach ( array( 'ideal', 'sofort', 'bancontact' ) as $payment_method_type ) {
315 382 if ( ! empty( $payment_method_details->$payment_method_type ) ) {
316 383 return $payment_method_details->$payment_method_type->generated_sepa_debit;
317 384 }
@@ -332,8 +399,10 @@
332 399 *
333 400 * @since 6.5, introduced in v3.0 of the Stripe add on.
334 401 *
335 402 * @param array $atts {
403 + * The details needs to create a payment.
404 + *
336 405 * @type stdClass $form
337 406 * @type stdClass $entry
338 407 * @type WP_Post $action
339 408 * @type string $amount
@@ -338,31 +407,32 @@
338 407 * @type WP_Post $action
339 408 * @type string $amount
340 409 * @type object $customer
341 410 * }
342 - * @return void
411 + *
412 + * @return bool True on success, false on failure.
343 413 */
344 414 public static function create_pending_stripe_link_payment( $atts ) {
345 415 if ( empty( $atts['form'] ) || empty( $atts['entry'] ) || empty( $atts['action'] ) || ! isset( $atts['amount'] ) || empty( $atts['customer'] ) ) {
346 - return;
416 + return false;
347 417 }
348 418
349 419 $form = $atts['form'];
350 - $intent_id = self::verify_intent( $form->id );
420 + $action = $atts['action'];
421 + $intent_id = self::verify_intent( $form->id, $action );
351 422
352 423 if ( ! $intent_id ) {
353 - return;
424 + return false;
354 425 }
355 426
356 - $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
427 + $is_setup_intent = str_starts_with( $intent_id, 'seti_' );
357 428 $entry = $atts['entry'];
358 - $action = $atts['action'];
359 429 $amount = $atts['amount'];
360 430 $customer = $atts['customer'];
361 431
362 432 if ( ! $is_setup_intent ) {
363 433 // Update the amount and set the customer before confirming the payment.
364 - FrmStrpLiteAppHelper::call_stripe_helper_class(
434 + $updated = FrmStrpLiteAppHelper::call_stripe_helper_class(
365 435 'update_intent',
366 436 $intent_id,
367 437 array(
368 438 'amount' => $amount,
@@ -368,14 +438,18 @@
368 438 'amount' => $amount,
369 439 'customer' => $customer->id,
370 440 )
371 441 );
442 +
443 + if ( ! $updated ) {
444 + return false;
445 + }
372 446 }
373 447
374 448 self::add_temporary_referer_meta( (int) $entry->id );
375 449
376 450 $frm_payment = new FrmTransLitePayment();
377 - $frm_payment->create(
451 + $payment_id = $frm_payment->create(
378 452 array(
379 453 'paysys' => 'stripe',
380 454 'amount' => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $amount, $action ),
381 455 'status' => 'pending',
@@ -382,10 +456,13 @@
382 456 'item_id' => $entry->id,
383 457 'action_id' => $action->ID,
384 458 'receipt_id' => $intent_id,
385 459 'sub_id' => '',
460 + 'test' => 'test' === FrmStrpLiteAppHelper::active_mode() ? 1 : 0,
386 461 )
387 462 );
463 +
464 + return (bool) $payment_id;
388 465 }
389 466
390 467 /**
391 468 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
@@ -391,13 +468,16 @@
391 468 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
392 469 *
393 470 * @since 6.5, introduced in v3.0 of the Stripe add on.
394 471 *
395 - * @param string|int $form_id
396 - * @return string|false String intent id on success, False if intent is missing or cannot be verified.
472 + * @param int|string $form_id
473 + * @param WP_Post $action
474 + *
475 + * @return false|string String intent id on success, False if intent is missing or cannot be verified.
397 476 */
398 - private static function verify_intent( $form_id ) {
477 + private static function verify_intent( $form_id, $action ) {
399 478 $client_secrets = FrmAppHelper::get_post_param( 'frmintent' . $form_id, array(), 'sanitize_text_field' );
479 +
400 480 if ( ! $client_secrets ) {
401 481 return false;
402 482 }
403 483
@@ -403,15 +483,27 @@
403 483
404 484 $client_secret = reset( $client_secrets );
405 485 list( $prefix, $intent_id ) = explode( '_', $client_secret );
406 486 $intent_id = $prefix . '_' . $intent_id;
487 + $is_setup_intent = str_starts_with( $intent_id, 'seti_' );
488 + $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
489 + $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
407 490
408 - $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
491 + if ( ! $intent || $intent->client_secret !== $client_secret || ! self::intent_matches_form_action( $intent, $action ) ) {
492 + return false;
493 + }
409 494
410 - $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
411 - $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
495 + if ( isset( $intent->charges ) && is_object( $intent->charges ) && ! empty( $intent->charges->data ) ) {
496 + // The intent should not have any charges yet.
497 + // If it does, the intent is invalid.
498 + return false;
499 + }
412 500
413 - if ( ! $intent || $intent->client_secret !== $client_secret ) {
501 + $frm_payment = new FrmTransLitePayment();
502 + $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
503 +
504 + if ( $payment ) {
505 + // A duplicate payment should not exist.
414 506 return false;
415 507 }
416 508
417 509 return $intent_id;
@@ -417,8 +509,27 @@
417 509 return $intent_id;
418 510 }
419 511
420 512 /**
513 + * Check if an intent matches a form action.
514 + *
515 + * @since 6.29
516 + *
517 + * @param object $intent
518 + * @param WP_Post $action
519 + *
520 + * @return bool
521 + */
522 + private static function intent_matches_form_action( $intent, $action ) {
523 + if ( ! isset( $intent->metadata ) || ! is_object( $intent->metadata ) || empty( $intent->metadata->action ) ) {
524 + // Avoid false positive if the intent is missing metadata.
525 + return true;
526 + }
527 +
528 + return (int) $intent->metadata->action === $action->ID;
529 + }
530 +
531 + /**
421 532 * Set the referer URL as field ID 0 in entry meta.
422 533 * This is required for iDEAL, sofort, and other payment methods that include an additional redirect step.
423 534 * It is used for the redirect in FrmStrpLinkRedirectHelper.
424 535 * It is deleted after the redirect happens.
@@ -423,8 +534,9 @@
423 534 * It is used for the redirect in FrmStrpLinkRedirectHelper.
424 535 * It is deleted after the redirect happens.
425 536 *
426 537 * @param int $entry_id
538 + *
427 539 * @return void
428 540 */
429 541 private static function add_temporary_referer_meta( $entry_id ) {
430 542 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
@@ -434,8 +546,9 @@
434 546 'payment_intent_client_secret',
435 547 'setup_intent',
436 548 'setup_intent_client_secret',
437 549 );
550 +
438 551 foreach ( $query_args_to_strip_from_referer as $arg ) {
439 552 $referer = remove_query_arg( $arg, $referer );
440 553 }
441 554
@@ -448,8 +561,9 @@
448 561 *
449 562 * @since 6.5, introduced in v3.0 of the Stripe add on.
450 563 *
451 564 * @param stdClass $form
565 + *
452 566 * @return void
453 567 */
454 568 public static function add_form_classes( $form ) {
455 569 if ( false === FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
@@ -464,8 +578,9 @@
464 578 *
465 579 * @since 6.5, introduced in v3.0 of the Stripe add on.
466 580 *
467 581 * @param mixed $form
582 + *
468 583 * @return mixed
469 584 */
470 585 public static function force_ajax_submit_for_stripe_link( $form ) {
471 586 if ( ! is_object( $form ) ) {