| @@ -27,8 +27,30 @@ | ||
| 27 | 27 | use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\StripeSettings; |
| 28 | 28 | |
| 29 | 29 | class PaymentHandler |
| 30 | 30 | { |
| 31 | + /** | |
| 32 | + * A reversed order (refund/partial-refund/cancel) must not fire the deferred | |
| 33 | + * submission action pipeline (notifications, integrations, user registration) | |
| 34 | + * when it is later reached via a double-opt-in / admin-approval confirmation or a | |
| 35 | + * subscription webhook. The normal paid flow is unaffected (latch already set), | |
| 36 | + * as are non-payment forms and unsettled-but-not-reversed states (pending/failed). | |
| 37 | + */ | |
| 38 | + public function skipActionsForReversedPayment($shouldProcess, $submission, $form) | |
| 39 | + { | |
| 40 | + if (!$shouldProcess || empty($form->has_payment)) { | |
| 41 | + return $shouldProcess; | |
| 42 | + } | |
| 43 | + | |
| 44 | + $paymentStatus = isset($submission->payment_status) ? $submission->payment_status : null; | |
| 45 | + | |
| 46 | + if (PaymentHelper::isReversedPaymentStatus($paymentStatus)) { | |
| 47 | + return false; | |
| 48 | + } | |
| 49 | + | |
| 50 | + return $shouldProcess; | |
| 51 | + } | |
| 52 | + | |
| 31 | 53 | public function init() |
| 32 | 54 | { |
| 33 | 55 | |
| 34 | 56 | add_filter('fluentform/global_settings_components', [$this, 'pushGlobalSettings'], 1, 1); |
| @@ -35,9 +57,11 @@ | ||
| 35 | 57 | |
| 36 | 58 | add_filter('fluentform/global_settings_component_settings_data', [$this, 'getGlobalSettingsPaymentVars']); |
| 37 | 59 | |
| 38 | 60 | add_action('wp_ajax_fluentform_handle_payment_ajax_endpoint', [$this, 'handleAjaxEndpoints']); |
| 39 | - | |
| 61 | + | |
| 62 | + add_filter('fluentform/should_process_submission_actions', [$this, 'skipActionsForReversedPayment'], 10, 3); | |
| 63 | + | |
| 40 | 64 | if (!$this->isEnabled()) { |
| 41 | 65 | return; |
| 42 | 66 | } |
| 43 | 67 | |
| @@ -395,16 +419,20 @@ | ||
| 395 | 419 | public function handleAjaxEndpoints() |
| 396 | 420 | { |
| 397 | 421 | // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified by Acl::verify() |
| 398 | 422 | $route = isset($_REQUEST['route']) ? sanitize_text_field(wp_unslash($_REQUEST['route'])) : ''; |
| 399 | - $formScopedRoutes = [ | |
| 400 | - 'get_form_settings', | |
| 401 | - 'save_form_settings', | |
| 423 | + $paymentMutationRoutes = [ | |
| 402 | 424 | 'update_transaction', |
| 403 | 425 | 'cancel_subscription' |
| 404 | 426 | ]; |
| 427 | + $formSettingRoutes = [ | |
| 428 | + 'get_form_settings', | |
| 429 | + 'save_form_settings' | |
| 430 | + ]; | |
| 405 | 431 | |
| 406 | - if (in_array($route, $formScopedRoutes, true)) { | |
| 432 | + if (in_array($route, $paymentMutationRoutes, true)) { | |
| 433 | + Acl::verify('fluentform_manage_payments', $this->resolveRouteFormId($route)); | |
| 434 | + } elseif (in_array($route, $formSettingRoutes, true)) { | |
| 407 | 435 | Acl::verify('fluentform_forms_manager', $this->resolveRouteFormId($route)); |
| 408 | 436 | } else { |
| 409 | 437 | Acl::verify('fluentform_settings_manager'); |
| 410 | 438 | } |
| @@ -612,9 +640,14 @@ | ||
| 612 | 640 | |
| 613 | 641 | $submissionIds = array_unique($submissionIds); |
| 614 | 642 | $transactionIds = array_unique($transactionIds); |
| 615 | 643 | |
| 644 | + // Claim only unowned submissions; payer_email is unverified and may match a registered owner. | |
| 616 | 645 | \FluentForm\App\Models\Submission::whereIn('id', $submissionIds) |
| 646 | + ->where(function ($query) { | |
| 647 | + $query->whereNull('user_id') | |
| 648 | + ->orWhere('user_id', ''); | |
| 649 | + }) | |
| 617 | 650 | ->update([ |
| 618 | 651 | 'user_id' => $userId, |
| 619 | 652 | 'updated_at' => current_time('mysql') |
| 620 | 653 | ]); |
| @@ -681,9 +714,9 @@ | ||
| 681 | 714 | if ('yes' === ArrayHelper::get($selectedPlan, 'user_input')) { |
| 682 | 715 | $userGivenValue = ArrayHelper::get($formData, "{$field['name']}_custom_$selectedPlanIndex"); |
| 683 | 716 | $userGivenValue = $userGivenValue ?: 0; |
| 684 | 717 | $planMinValue = ArrayHelper::get($selectedPlan, 'user_input_min_value'); |
| 685 | - if (!is_numeric($userGivenValue) || ($planMinValue && $userGivenValue < $planMinValue)) { | |
| 718 | + if (!is_numeric($userGivenValue) || $userGivenValue < 0 || ($planMinValue && $userGivenValue < $planMinValue)) { | |
| 686 | 719 | $error = __('This subscription plan value is invalid', 'fluentform'); |
| 687 | 720 | } |
| 688 | 721 | } |
| 689 | 722 | } |