PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.15
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.15
6.2.15 6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 All 197 releases
fluentform / app / Modules / Payments / PaymentHandler.php

PaymentHandler.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.15, at app/Modules/Payments/PaymentHandler.php

787 lines 32.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Modules\Payments;
4
5 if (!defined('ABSPATH')) {
6 exit; // Exit if accessed directly.
7 }
8
9 use FluentForm\App\Helpers\Helper;
10 use FluentForm\App\Modules\Acl\Acl;
11 use FluentForm\App\Modules\Form\FormFieldsParser;
12 use FluentForm\App\Modules\Payments\Orders\OrderData;
13 use FluentForm\App\Services\FormBuilder\ShortCodeParser;
14 use FluentForm\Framework\Helpers\ArrayHelper;
15 use FluentForm\App\Modules\Payments\Classes\PaymentAction;
16 use FluentForm\App\Modules\Payments\Classes\PaymentEntries;
17 use FluentForm\App\Modules\Payments\Classes\PaymentReceipt;
18 use FluentForm\App\Modules\Payments\Components\CustomPaymentComponent;
19 use FluentForm\App\Modules\Payments\Components\ItemQuantity;
20 use FluentForm\App\Modules\Payments\Components\MultiPaymentComponent;
21 use FluentForm\App\Modules\Payments\Components\PaymentMethods;
22 use FluentForm\App\Modules\Payments\Components\PaymentSummaryComponent;
23 use FluentForm\App\Modules\Payments\Components\Subscription;
24 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\Components\StripeInline;
25 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\ConnectConfig;
26 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\StripeHandler;
27 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\StripeSettings;
28
29 class PaymentHandler
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
53 public function init()
54 {
55
56 add_filter('fluentform/global_settings_components', [$this, 'pushGlobalSettings'], 1, 1);
57
58 add_filter('fluentform/global_settings_component_settings_data', [$this, 'getGlobalSettingsPaymentVars']);
59
60 add_action('wp_ajax_fluentform_handle_payment_ajax_endpoint', [$this, 'handleAjaxEndpoints']);
61
62 add_filter('fluentform/should_process_submission_actions', [$this, 'skipActionsForReversedPayment'], 10, 3);
63
64 if (!$this->isEnabled()) {
65 return;
66 }
67
68 add_filter('fluentform/show_payment_entries', '__return_true');
69
70 add_filter('fluentform/form_settings_menu', array($this, 'maybeAddPaymentSettings'), 10, 2);
71 // Let's load Payment Methods here
72 (new StripeHandler())->init();
73
74 // Let's load the payment method component here
75 new MultiPaymentComponent();
76 new Subscription();
77 new CustomPaymentComponent();
78 new ItemQuantity();
79 new PaymentMethods();
80 new PaymentSummaryComponent();
81
82 add_filter('fluentform/editor_components', function ($components) {
83 if (!Helper::hasPro()) {
84 $components['payments'][] = [
85 'index' => 6,
86 'element' => 'payment_coupon',
87 'attributes' => [],
88 'settings' => [],
89 'editor_options' => [
90 'title' => __('Coupon', 'fluentform'),
91 'icon_class' => 'el-icon-postcard',
92 'template' => 'inputText',
93 ],
94 ];
95 }
96 return $components;
97 }, 11);
98
99 new StripeInline();
100
101 add_action('fluentform/before_insert_payment_form', array($this, 'maybeHandlePayment'), 10, 3);
102
103 add_filter('fluentform/submission_order_data', function ($data, $submission) {
104 return OrderData::getSummary($submission, $submission->form);
105 }, 10, 2);
106
107 add_filter('fluentform/entries_vars', function ($vars, $form) {
108 if ($form->has_payment) {
109 $vars['has_payment'] = $form->has_payment;
110 $vars['currency_config'] = PaymentHelper::getCurrencyConfig($form->id);
111 $vars['currency_symbols'] = PaymentHelper::getCurrencySymbols();
112 $vars['payment_statuses'] = PaymentHelper::getPaymentStatuses();
113 }
114 return $vars;
115 }, 10, 2);
116
117 add_filter(
118 'fluentform/submission_labels',
119 [$this, 'modifySingleEntryLabels'],
120 10,
121 3
122 );
123
124
125 add_filter('fluentform/all_entry_labels_with_payment', array($this, 'modifySingleEntryLabels'), 10, 3);
126
127 add_action('fluentform/rendering_payment_form', function ($form) {
128 if (Helper::isElementorEditor()) {
129 return;
130 }
131
132 $src = fluentFormMix('js/payment_handler.js');
133 $version = FLUENTFORM_VERSION;
134
135 // If pro is installed and script is compatible, load script from pro
136 if (Helper::isProPaymentScriptCompatible()) {
137 $src = FLUENTFORMPRO_DIR_URL . 'public/js/payment_handler_pro.js';
138 $version = FLUENTFORMPRO_VERSION;
139 }
140
141 wp_enqueue_script('fluentform-payment-handler',
142 $src,
143 array('jquery'),
144 $version,
145 true
146 );
147
148 wp_enqueue_style(
149 'fluentform-payment-skin',
150 fluentFormMix('css/payment_skin.css'),
151 array(),
152 FLUENTFORM_VERSION
153 );
154
155 wp_localize_script('fluentform-payment-handler', 'fluentform_payment_config', [
156 'i18n' => [
157 'item' => __('Item', 'fluentform'),
158 'price' => __('Price', 'fluentform'),
159 'qty' => __('Qty', 'fluentform'),
160 'line_total' => __('Line Total', 'fluentform'),
161 'total' => __('Total', 'fluentform'),
162 'not_found' => __('No payment item selected yet', 'fluentform'),
163 'discount:' => __('Discount:', 'fluentform'),
164 'processing_text' => __('Processing payment. Please wait...', 'fluentform'),
165 'confirming_text' => __('Confirming payment. Please wait...', 'fluentform'),
166 /* translators: %s is the item name */
167 'Signup Fee for %s' => __('Signup Fee for %s', 'fluentform'),
168 /* translators: 1: item name, 2: plan name */
169 'Signup Fee for %1$s - %2$s' => __('Signup Fee for %1$s - %2$s', 'fluentform'),
170 ]
171 ]);
172
173 $secretKey = apply_filters_deprecated(
174 'fluentform-payment_stripe_publishable_key',
175 [
176 StripeSettings::getPublishableKey($form->id),
177 $form->id
178 ],
179 FLUENTFORM_FRAMEWORK_UPGRADE,
180 'fluentform/payment_stripe_publishable_key',
181 'Use fluentform/payment_stripe_publishable_key instead of fluentform-payment_stripe_publishable_key.'
182 );
183
184 $publishableKey = apply_filters('fluentform/payment_stripe_publishable_key', $secretKey, $form->id);
185
186 $stripeCustomCss = [
187 'styles' => [
188 'base' => [
189 'backgroundColor' => 'white',
190 'color' => '#32325d',
191 'fontFamily' => "-apple-system, \"system-ui\", \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif",
192 'fontSize' => '14px',
193 'fontSmoothing' => 'antialiased',
194 'iconColor' => '#32325d',
195 'textDecoration' => 'none',
196 '::placeholder' => [
197 'color'=> "#aab7c4"
198 ],
199 ":focus" => [
200 'backgroundColor' => 'white',
201 'color' => '#32325d',
202 'fontFamily' => "-apple-system, \"system-ui\", \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif",
203 'fontSize' => '14px',
204 'fontSmoothing' => 'antialiased',
205 'iconColor' => '#32325d',
206 'textDecoration' => 'none',
207 ],
208 ],
209 'invalid' => [
210 'color' => "#fa755a",
211 'iconColor' => "#fa755a"
212 ]
213 ]
214 ];
215
216 $paymentConfig = [
217 'currency_settings' => PaymentHelper::getCurrencyConfig($form->id),
218 'stripe' => [
219 'publishable_key' => $publishableKey,
220 'inlineConfig' => PaymentHelper::getStripeInlineConfig($form->id),
221 'custom_style' => apply_filters(
222 'fluentform/stripe_inline_custom_css',
223 $stripeCustomCss,
224 $form->id
225 ),
226 'locale' => 'en'
227 ],
228 'stripe_app_info' => [
229 'name' => 'Fluent Forms',
230 'version' => FLUENTFORM_VERSION,
231 'url' => site_url(),
232 'partner_id' => 'pp_partner_FN62GfRLM2Kx5d'
233 ],
234 ];
235 $paymentConfig = apply_filters('fluentform/payment_config', $paymentConfig, $form->id);
236
237 wp_localize_script('fluentform-payment-handler', 'fluentform_payment_config_' . $form->id, $paymentConfig);
238
239 }, 11, 1);
240
241 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Public payment callback from gateway, nonce not applicable, verified by transaction_hash in validateFrameLessPage()
242 if (isset($_GET['fluentform_payment']) && isset($_GET['payment_method'])) {
243 add_action('wp', function () {
244 $data = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data sanitized below and verified by validateFrameLessPage()
245
246 $type = isset($_GET['fluentform_payment']) ? sanitize_text_field(wp_unslash($_GET['fluentform_payment'])) : '';
247
248 if ($type == 'view' && $route = ArrayHelper::get($data, 'route')) {
249 do_action_deprecated(
250 'fluent_payment_view_' . $route,
251 [
252 $data
253 ],
254 FLUENTFORM_FRAMEWORK_UPGRADE,
255 'fluentform/payment_view_' . $route,
256 'Use fluentform/payment_view_' . $route . ' instead of fluent_payment_view_' . $route
257 );
258 do_action('fluentform/payment_view_' . $route, $data);
259 }
260
261 $this->validateFrameLessPage($data);
262 $paymentMethod = isset($_GET['payment_method']) ? sanitize_text_field(wp_unslash($_GET['payment_method'])) : '';
263 do_action_deprecated(
264 'fluent_payment_frameless_' . $paymentMethod,
265 [
266 $data
267 ],
268 FLUENTFORM_FRAMEWORK_UPGRADE,
269 'fluentform/payment_frameless_' . $paymentMethod,
270 'Use fluentform/payment_frameless_' . $paymentMethod . ' instead of fluent_payment_frameless_' . $paymentMethod
271 );
272 do_action('fluentform/payment_frameless_' . $paymentMethod, $data);
273 });
274 }
275 // phpcs:enable WordPress.Security.NonceVerification.Recommended -- End of payment callback processing
276
277 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- IPN callback from payment gateway, verified by gateway signature
278 if (isset($_REQUEST['fluentform_payment_api_notify'])) {
279 add_action('wp', function () {
280 $paymentMethod = isset($_REQUEST['payment_method']) ? sanitize_text_field(wp_unslash($_REQUEST['payment_method'])) : '';
281 do_action_deprecated(
282 'fluentform_ipn_endpoint_' . $paymentMethod,
283 [
284 ],
285 FLUENTFORM_FRAMEWORK_UPGRADE,
286 'fluentform/ipn_endpoint_' . $paymentMethod,
287 'Use fluentform/ipn_endpoint_' . $paymentMethod . ' instead of fluentform_ipn_endpoint_' . $paymentMethod
288 );
289 do_action('fluentform/ipn_endpoint_' . $paymentMethod);
290 });
291 }
292 // phpcs:enable WordPress.Security.NonceVerification.Recommended -- End of IPN webhook processing
293
294 add_filter('fluentform/editor_vars', function ($vars) {
295 $settings = PaymentHelper::getCurrencyConfig($vars['form_id']);
296 $vars['payment_settings'] = $settings;
297 $vars['has_payment_features'] = !!$settings;
298 return $vars;
299 });
300
301 add_filter('fluentform/admin_i18n', array($this, 'paymentTranslations'), 10, 1);
302
303 add_filter('fluentform/payment_smartcode', array($this, 'paymentReceiptView'), 10, 3);
304
305 add_action('user_register', array($this, 'maybeAssignTransactions'), 99, 1);
306
307 (new PaymentEntries())->init();
308
309 /*
310 * Transactions and subscriptions Shortcode
311 */
312 (new TransactionShortcodes())->init();
313
314 add_filter(
315 'fluentform/validate_input_item_subscription_payment_component',
316 [$this, 'validateSubscriptionInputs'],
317 10,
318 3
319 );
320
321 add_filter(
322 'fluentform/validate_input_item_multi_payment_component',
323 [$this, 'validatePaymentInputs'],
324 10,
325 3
326 );
327
328 add_filter('fluentform/validate_input_item_custom_payment_component', [$this, 'validatePaymentNumber'], 10, 3);
329 add_filter('fluentform/validate_input_item_item_quantity_component', [$this, 'validatePaymentNumber'], 10, 3);
330
331 add_filter(
332 'fluentform/validate_input_item_payment_method',
333 [$this, 'validatePaymentMethod'],
334 10,
335 5
336 );
337 }
338
339 public function pushGlobalSettings($components)
340 {
341 $subMenuItems = [
342 [
343 'title' => 'Settings',
344 'hash' => 'payments/general_settings',
345 ],
346 [
347 'title' => 'Payment Methods',
348 'hash' => 'payments/payment_methods',
349 ]
350 ];
351 $subMenuItems = apply_filters('fluentform/global_settings_payment_sub_menu_items', $subMenuItems);
352
353 $components['payment_settings'] = [
354 'title' => __('Payment Settings', 'fluentform'),
355 'sub_menu'=> $subMenuItems
356 ];
357 return $components;
358 }
359
360 public function getGlobalSettingsPaymentVars($globalSettingVars)
361 {
362 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Admin page, nonce verified by WordPress admin
363 if (isset($_GET['ff_stripe_connect'])) {
364 $data = ArrayHelper::only($_GET, ['ff_stripe_connect', 'mode', 'state', 'code']);
365 ConnectConfig::verifyAuthorizeSuccess($data);
366 }
367
368 $paymentSettings = PaymentHelper::getPaymentSettings();
369 $isSettingsAvailable = PaymentHelper::hasPaymentSettings();
370
371 $nav = 'general';
372
373 if (isset($_REQUEST['nav'])) {
374 $nav = sanitize_text_field(wp_unslash($_REQUEST['nav']));
375 }
376 // phpcs:enable WordPress.Security.NonceVerification.Recommended -- End of admin page GET parameter processing
377
378 $paymentMethods = apply_filters_deprecated(
379 'fluentformpro_available_payment_methods',
380 [
381 []
382 ],
383 FLUENTFORM_FRAMEWORK_UPGRADE,
384 'fluentform/available_payment_methods',
385 'Use fluentform/available_payment_methods instead of fluentformpro_available_payment_methods.'
386 );
387
388 $globalSettings = apply_filters_deprecated(
389 'fluentformpro_payment_methods_global_settings',
390 [
391 []
392 ],
393 FLUENTFORM_FRAMEWORK_UPGRADE,
394 'fluentform/payment_methods_global_settings',
395 'Use fluentform/payment_methods_global_settings instead of fluentformpro_payment_methods_global_settings.'
396 );
397
398 $paymentVars = [
399 'is_setup' => $isSettingsAvailable,
400 'general' => $paymentSettings,
401 'payment_methods' => apply_filters('fluentform/available_payment_methods', $paymentMethods),
402 'available_payment_methods' => apply_filters('fluentform/payment_methods_global_settings', $globalSettings),
403 'currencies' => PaymentHelper::getCurrencies(),
404 'active_nav' => $nav,
405 'stripe_webhook_url' => add_query_arg([
406 'fluentform_payment_api_notify' => '1',
407 'payment_method' => 'stripe'
408 ], site_url('index.php')),
409 'paypal_webhook_url' => add_query_arg([
410 'fluentform_payment_api_notify' => '1',
411 'payment_method' => 'paypal'
412 ], site_url('index.php'))
413 ];
414
415 // Enqueue payment global settings css
416 wp_enqueue_style('ff-payment-settings', fluentFormMix('css/payment_settings.css'), [], FLUENTFORM_VERSION);
417
418 $globalSettingVars['payment_vars'] = apply_filters('fluentform/global_settings_component_payment_vars', $paymentVars);
419 return $globalSettingVars;
420 }
421
422 public function handleAjaxEndpoints()
423 {
424 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified by Acl::verify()
425 $route = isset($_REQUEST['route']) ? sanitize_text_field(wp_unslash($_REQUEST['route'])) : '';
426 $paymentMutationRoutes = [
427 'update_transaction',
428 'cancel_subscription'
429 ];
430 $formSettingRoutes = [
431 'get_form_settings',
432 'save_form_settings'
433 ];
434
435 if (in_array($route, $paymentMutationRoutes, true)) {
436 Acl::verify('fluentform_manage_payments', $this->resolveRouteFormId($route));
437 } elseif (in_array($route, $formSettingRoutes, true)) {
438 Acl::verify('fluentform_forms_manager', $this->resolveRouteFormId($route));
439 } else {
440 Acl::verify('fluentform_settings_manager');
441 }
442 // phpcs:enable WordPress.Security.NonceVerification.Recommended -- End of AJAX endpoint nonce verification by Acl::verify()
443
444 (new AjaxEndpoints())->handleEndpoint($route);
445 }
446
447 private function resolveRouteFormId($route)
448 {
449 switch ($route) {
450 case 'get_form_settings':
451 case 'save_form_settings':
452 return Acl::verifyFormId(wpFluentForm()->request->get('form_id'));
453 case 'update_transaction':
454 return $this->resolveTransactionFormId();
455 case 'cancel_subscription':
456 return $this->resolveSubscriptionFormId();
457 default:
458 return null;
459 }
460 }
461
462 private function resolveTransactionFormId()
463 {
464 $transactionData = wpFluentForm()->request->get('transaction', []);
465 $transactionId = absint(ArrayHelper::get($transactionData, 'id'));
466
467 if (!$transactionId) {
468 return -1;
469 }
470
471 $transaction = \FluentForm\App\Models\Transaction::select('form_id')->find($transactionId);
472
473 return $transaction ? (int) $transaction->form_id : -1;
474 }
475
476 private function resolveSubscriptionFormId()
477 {
478 $subscriptionId = absint(wpFluentForm()->request->get('subscription_id'));
479
480 if (!$subscriptionId) {
481 return -1;
482 }
483
484 $subscription = \FluentForm\App\Models\Subscription::select('form_id')->find($subscriptionId);
485
486 return $subscription ? (int) $subscription->form_id : -1;
487 }
488
489 public function maybeHandlePayment($insertData, $data, $form)
490 {
491 // Let's get selected Payment Method
492 if (!FormFieldsParser::hasPaymentFields($form)) {
493 return;
494 }
495
496 $paymentAction = new PaymentAction($form, $insertData, $data);
497
498 if (!$paymentAction->getSubscriptionItems() && !$paymentAction->getCalculatedAmount()) {
499 $paymentAction->flagZeroTotalOrder();
500 return;
501 }
502
503 /*
504 * We have to check if
505 * 1. has payment method
506 * 2. if user selected payment method
507 * 3. or maybe has a conditional logic on it
508 */
509 if ($paymentAction->isConditionPass()) {
510 if (FormFieldsParser::hasElement($form, 'payment_method') &&
511 !$paymentAction->selectedPaymentMethod
512 ) {
513 wp_send_json([
514 'errors' => [__('Sorry! No selected payment method found. Please select a valid payment method', 'fluentform')]
515 ], 423);
516 }
517 }
518
519 /*
520 * Some Payment Gateway like Razorpay, Square not supported $subscriptionItems.
521 * So we are providing filter hook to validate payment fields.
522 */
523 $errors = apply_filters(
524 'fluentform/validate_payment_items_' . $paymentAction->selectedPaymentMethod,
525 [],
526 $paymentAction->getOrderItems(),
527 $paymentAction->getSubscriptionItems(),
528 $form
529 );
530
531 if ($errors) {
532 wp_send_json([
533 'errors' => $errors
534 ], 423);
535 }
536
537 $paymentAction->draftFormEntry();
538 }
539
540 public function isEnabled()
541 {
542 $paymentSettings = PaymentHelper::getPaymentSettings();
543 return $paymentSettings['status'] == 'yes';
544 }
545
546 public function modifySingleEntryLabels($labels, $submission, $form)
547 {
548 $formFields = FormFieldsParser::getPaymentFields($form);
549 if ($formFields && is_array($formFields)) {
550 $labels = ArrayHelper::except($labels, array_keys($formFields));
551 }
552 return $labels;
553 }
554
555 public function maybeAddPaymentSettings($menus, $formId)
556 {
557 $form = \FluentForm\App\Models\Form::find($formId);
558 if ($form->has_payment) {
559 $menus = array_merge(array_slice($menus, 0, 1), array(
560 'payment_settings' => [
561 'title' => __('Payment Settings', 'fluentform'),
562 'slug' => 'form_settings',
563 'hash' => 'payment_settings',
564 'route' => '/payment-settings',
565 ]
566 ), array_slice($menus, 1));
567 }
568 return $menus;
569 }
570
571
572 /**
573 * @param $html string
574 * @param $property string
575 * @param $instance ShortCodeParser
576 * @return false|string
577 */
578 public function paymentReceiptView($html, $property, $instance)
579 {
580 $entry = $instance::getEntry();
581 $receiptClass = new PaymentReceipt($entry);
582 return $receiptClass->getItem($property);
583 }
584
585 private function validateFrameLessPage($data)
586 {
587 // We should verify the transaction hash from the URL
588 $transactionHash = sanitize_text_field(ArrayHelper::get($data, 'transaction_hash'));
589 $submissionId = intval(ArrayHelper::get($data, 'fluentform_payment'));
590 if (!$submissionId) {
591 die('Validation Failed');
592 }
593
594 if ($transactionHash) {
595 $transaction = \FluentForm\App\Models\Transaction::bySubmission($submissionId)
596 ->where('transaction_hash', $transactionHash)
597 ->first();
598 if ($transaction) {
599 return true;
600 }
601
602 die('Transaction hash is invalid');
603 }
604
605 $uid = sanitize_text_field(ArrayHelper::get($data, 'entry_uid'));
606 if (!$uid) {
607 die('Validation Failed');
608 }
609
610 $originalUid = Helper::getSubmissionMeta($submissionId, '_entry_uid_hash');
611
612 if ($originalUid != $uid) {
613 die(esc_html__('Transaction UID is invalid', 'fluentform'));
614 }
615
616 return true;
617 }
618
619 public function maybeAssignTransactions($userId)
620 {
621 $user = get_user_by('ID', $userId);
622 if (!$user) {
623 return false;
624 }
625 $userEmail = $user->user_email;
626
627 $transactions = \FluentForm\App\Models\Transaction::where('payer_email', $userEmail)
628 ->where(function ($query) {
629 $query->whereNull('user_id')
630 ->orWhere('user_id', '');
631 })
632 ->get();
633
634 if (count($transactions) === 0) {
635 return false;
636 }
637
638 $submissionIds = [];
639 $transactionIds = [];
640 foreach ($transactions as $transaction) {
641 $submissionIds[] = $transaction->submission_id;
642 $transactionIds[] = $transaction->id;
643 }
644
645 $submissionIds = array_unique($submissionIds);
646 $transactionIds = array_unique($transactionIds);
647
648 // Claim only unowned submissions; payer_email is unverified and may match a registered owner.
649 \FluentForm\App\Models\Submission::whereIn('id', $submissionIds)
650 ->where(function ($query) {
651 $query->whereNull('user_id')
652 ->orWhere('user_id', '');
653 })
654 ->update([
655 'user_id' => $userId,
656 'updated_at' => current_time('mysql')
657 ]);
658
659 \FluentForm\App\Models\Transaction::whereIn('id', $transactionIds)
660 ->update([
661 'user_id' => $userId,
662 'updated_at' => current_time('mysql')
663 ]);
664
665 return true;
666 }
667
668 public function paymentTranslations($i18n)
669 {
670 $paymentI18n = array(
671 'Order Details' => __('Order Details', 'fluentform'),
672 'Product' => __('Product', 'fluentform'),
673 'Qty' => __('Qty', 'fluentform'),
674 'Unit Price' => __('Unit Price', 'fluentform'),
675 'Total' => __('Total', 'fluentform'),
676 'Sub-Total' => __('Sub-Total', 'fluentform'),
677 'Discount' => __('Discount', 'fluentform'),
678 'Price' => __('Price', 'fluentform'),
679 'Payment Details' => __('Payment Details', 'fluentform'),
680 'From Subscriptions' => __('From Subscriptions', 'fluentform'),
681 'Card Last 4' => __('Card Last 4', 'fluentform'),
682 'Payment Total' => __('Payment Total', 'fluentform'),
683 'Payment Status' => __('Payment Status', 'fluentform'),
684 'Transaction ID' => __('Transaction ID', 'fluentform'),
685 'Payment Method' => __('Payment Method', 'fluentform'),
686 'Transaction' => __('Transaction', 'fluentform'),
687 'Refunds' => __('Refunds', 'fluentform'),
688 'Refund' => __('Refund', 'fluentform'),
689 'at' => __('at', 'fluentform'),
690 'View' => __('View', 'fluentform'),
691 'has been refunded via' => __('has been refunded via', 'fluentform'),
692 'Note' => __('Note', 'fluentform'),
693 'Edit Transaction' => __('Edit Transaction', 'fluentform'),
694 'Billing Name' => __('Billing Name', 'fluentform'),
695 'Billing Email' => __('Billing Email', 'fluentform'),
696 'Billing Address' => __('Billing Address', 'fluentform'),
697 'Shipping Address' => __('Shipping Address', 'fluentform'),
698 'Reference ID' => __('Reference ID', 'fluentform'),
699 'refunds-to-be-handled-from-provider-text' => __('Please note that, Actual Refund needs to be handled in your Payment Service Provider.', 'fluentform'),
700 'Please Provide new refund amount only.' => __('Please Provide new refund amount only.', 'fluentform'),
701 'Refund Note' => __('Refund Note', 'fluentform'),
702 'Cancel' => __('Cancel', 'fluentform'),
703 'Confirm' => __('Confirm', 'fluentform'),
704 );
705 return array_merge($i18n,$paymentI18n);
706 }
707
708 public function validateSubscriptionInputs($error, $field, $formData)
709 {
710 if (isset($formData[$field['name']])) {
711 $subscriptionOptions = ArrayHelper::get($field, 'raw.settings.subscription_options', []);
712 $selectedPlanIndex = $formData[$field['name']];
713 $acceptedSubscriptionPlan = is_numeric($selectedPlanIndex) && in_array($selectedPlanIndex, array_keys($subscriptionOptions));
714 if (!$acceptedSubscriptionPlan) {
715 $error = __('This subscription plan is invalid', 'fluentform');
716 }
717 $selectedPlan = ArrayHelper::get($subscriptionOptions, $selectedPlanIndex, []);
718 if ('yes' === ArrayHelper::get($selectedPlan, 'user_input')) {
719 $userGivenValue = ArrayHelper::get($formData, "{$field['name']}_custom_$selectedPlanIndex");
720 $userGivenValue = $userGivenValue ?: 0;
721 $planMinValue = ArrayHelper::get($selectedPlan, 'user_input_min_value');
722 if (!is_numeric($userGivenValue) || $userGivenValue < 0 || ($planMinValue && $userGivenValue < $planMinValue)) {
723 $error = __('This subscription plan value is invalid', 'fluentform');
724 }
725 }
726 }
727
728 return $error;
729 }
730
731 public function validatePaymentInputs($error, $field, $formData)
732 {
733 // A submitted "0" is still a value and must match an offered option.
734 if (!in_array(ArrayHelper::get($formData, $field['name']), [null, '', []], true)) {
735 $fieldType = ArrayHelper::get($field, 'raw.attributes.type');
736
737 if (in_array($fieldType, ['radio', 'select', 'checkbox'])) {
738 $pricingOptions = array_column(
739 ArrayHelper::get($field, 'raw.settings.pricing_options', []),
740 'label'
741 );
742
743 $pricingOptions = array_map('sanitize_text_field', $pricingOptions);
744
745 if (in_array($fieldType, ['radio', 'select'])) {
746 $acceptedPaymentPlan = in_array($formData[$field['name']], $pricingOptions);
747 } else {
748 $acceptedPaymentPlan = array_diff((array) $formData[$field['name']], $pricingOptions);
749
750 $acceptedPaymentPlan = empty($acceptedPaymentPlan);
751 }
752
753 if (!$acceptedPaymentPlan) {
754 $error = __('This payment item is invalid', 'fluentform');
755 }
756 }
757 }
758
759 return $error;
760 }
761
762 // The order builder silently drops an amount or quantity it cannot price, zeroing the order.
763 public function validatePaymentNumber($error, $field, $formData)
764 {
765 $value = ArrayHelper::get($formData, $field['name']);
766 if ($error || in_array($value, [null, ''], true) || (is_numeric($value) && $value > 0)) {
767 return $error;
768 }
769
770 $isOptionalZero = is_numeric($value) && 0 == $value
771 && !ArrayHelper::get($field, 'raw.settings.validation_rules.required.value');
772
773 return $isOptionalZero ? $error : __('This payment item is invalid', 'fluentform');
774 }
775
776 public function validatePaymentMethod($error, $field, $formData, $fields, $form)
777 {
778 if ($selectedMethod = ArrayHelper::get($formData, $field['name'])) {
779 $activeMethods = array_keys(PaymentHelper::getFormPaymentMethods($form->id));
780 if (!in_array($selectedMethod, $activeMethods)) {
781 $error = __('This payment method is invalid', 'fluentform');
782 }
783 }
784 return $error;
785 }
786 }
787