PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.14
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.14
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 3.6.65 All 196 releases
fluentform / app / Modules / Payments / PaymentHandler.php

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

768 lines 31.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(
329 'fluentform/validate_input_item_payment_method',
330 [$this, 'validatePaymentMethod'],
331 10,
332 5
333 );
334 }
335
336 public function pushGlobalSettings($components)
337 {
338 $subMenuItems = [
339 [
340 'title' => 'Settings',
341 'hash' => 'payments/general_settings',
342 ],
343 [
344 'title' => 'Payment Methods',
345 'hash' => 'payments/payment_methods',
346 ]
347 ];
348 $subMenuItems = apply_filters('fluentform/global_settings_payment_sub_menu_items', $subMenuItems);
349
350 $components['payment_settings'] = [
351 'title' => __('Payment Settings', 'fluentform'),
352 'sub_menu'=> $subMenuItems
353 ];
354 return $components;
355 }
356
357 public function getGlobalSettingsPaymentVars($globalSettingVars)
358 {
359 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Admin page, nonce verified by WordPress admin
360 if (isset($_GET['ff_stripe_connect'])) {
361 $data = ArrayHelper::only($_GET, ['ff_stripe_connect', 'mode', 'state', 'code']);
362 ConnectConfig::verifyAuthorizeSuccess($data);
363 }
364
365 $paymentSettings = PaymentHelper::getPaymentSettings();
366 $isSettingsAvailable = PaymentHelper::hasPaymentSettings();
367
368 $nav = 'general';
369
370 if (isset($_REQUEST['nav'])) {
371 $nav = sanitize_text_field(wp_unslash($_REQUEST['nav']));
372 }
373 // phpcs:enable WordPress.Security.NonceVerification.Recommended -- End of admin page GET parameter processing
374
375 $paymentMethods = apply_filters_deprecated(
376 'fluentformpro_available_payment_methods',
377 [
378 []
379 ],
380 FLUENTFORM_FRAMEWORK_UPGRADE,
381 'fluentform/available_payment_methods',
382 'Use fluentform/available_payment_methods instead of fluentformpro_available_payment_methods.'
383 );
384
385 $globalSettings = apply_filters_deprecated(
386 'fluentformpro_payment_methods_global_settings',
387 [
388 []
389 ],
390 FLUENTFORM_FRAMEWORK_UPGRADE,
391 'fluentform/payment_methods_global_settings',
392 'Use fluentform/payment_methods_global_settings instead of fluentformpro_payment_methods_global_settings.'
393 );
394
395 $paymentVars = [
396 'is_setup' => $isSettingsAvailable,
397 'general' => $paymentSettings,
398 'payment_methods' => apply_filters('fluentform/available_payment_methods', $paymentMethods),
399 'available_payment_methods' => apply_filters('fluentform/payment_methods_global_settings', $globalSettings),
400 'currencies' => PaymentHelper::getCurrencies(),
401 'active_nav' => $nav,
402 'stripe_webhook_url' => add_query_arg([
403 'fluentform_payment_api_notify' => '1',
404 'payment_method' => 'stripe'
405 ], site_url('index.php')),
406 'paypal_webhook_url' => add_query_arg([
407 'fluentform_payment_api_notify' => '1',
408 'payment_method' => 'paypal'
409 ], site_url('index.php'))
410 ];
411
412 // Enqueue payment global settings css
413 wp_enqueue_style('ff-payment-settings', fluentFormMix('css/payment_settings.css'), [], FLUENTFORM_VERSION);
414
415 $globalSettingVars['payment_vars'] = apply_filters('fluentform/global_settings_component_payment_vars', $paymentVars);
416 return $globalSettingVars;
417 }
418
419 public function handleAjaxEndpoints()
420 {
421 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified by Acl::verify()
422 $route = isset($_REQUEST['route']) ? sanitize_text_field(wp_unslash($_REQUEST['route'])) : '';
423 $paymentMutationRoutes = [
424 'update_transaction',
425 'cancel_subscription'
426 ];
427 $formSettingRoutes = [
428 'get_form_settings',
429 'save_form_settings'
430 ];
431
432 if (in_array($route, $paymentMutationRoutes, true)) {
433 Acl::verify('fluentform_manage_payments', $this->resolveRouteFormId($route));
434 } elseif (in_array($route, $formSettingRoutes, true)) {
435 Acl::verify('fluentform_forms_manager', $this->resolveRouteFormId($route));
436 } else {
437 Acl::verify('fluentform_settings_manager');
438 }
439 // phpcs:enable WordPress.Security.NonceVerification.Recommended -- End of AJAX endpoint nonce verification by Acl::verify()
440
441 (new AjaxEndpoints())->handleEndpoint($route);
442 }
443
444 private function resolveRouteFormId($route)
445 {
446 switch ($route) {
447 case 'get_form_settings':
448 case 'save_form_settings':
449 return Acl::verifyFormId(wpFluentForm()->request->get('form_id'));
450 case 'update_transaction':
451 return $this->resolveTransactionFormId();
452 case 'cancel_subscription':
453 return $this->resolveSubscriptionFormId();
454 default:
455 return null;
456 }
457 }
458
459 private function resolveTransactionFormId()
460 {
461 $transactionData = wpFluentForm()->request->get('transaction', []);
462 $transactionId = absint(ArrayHelper::get($transactionData, 'id'));
463
464 if (!$transactionId) {
465 return -1;
466 }
467
468 $transaction = \FluentForm\App\Models\Transaction::select('form_id')->find($transactionId);
469
470 return $transaction ? (int) $transaction->form_id : -1;
471 }
472
473 private function resolveSubscriptionFormId()
474 {
475 $subscriptionId = absint(wpFluentForm()->request->get('subscription_id'));
476
477 if (!$subscriptionId) {
478 return -1;
479 }
480
481 $subscription = \FluentForm\App\Models\Subscription::select('form_id')->find($subscriptionId);
482
483 return $subscription ? (int) $subscription->form_id : -1;
484 }
485
486 public function maybeHandlePayment($insertData, $data, $form)
487 {
488 // Let's get selected Payment Method
489 if (!FormFieldsParser::hasPaymentFields($form)) {
490 return;
491 }
492
493 $paymentAction = new PaymentAction($form, $insertData, $data);
494
495 if (!$paymentAction->getSubscriptionItems() && !$paymentAction->getCalculatedAmount()) {
496 return;
497 }
498
499 /*
500 * We have to check if
501 * 1. has payment method
502 * 2. if user selected payment method
503 * 3. or maybe has a conditional logic on it
504 */
505 if ($paymentAction->isConditionPass()) {
506 if (FormFieldsParser::hasElement($form, 'payment_method') &&
507 !$paymentAction->selectedPaymentMethod
508 ) {
509 wp_send_json([
510 'errors' => [__('Sorry! No selected payment method found. Please select a valid payment method', 'fluentform')]
511 ], 423);
512 }
513 }
514
515 /*
516 * Some Payment Gateway like Razorpay, Square not supported $subscriptionItems.
517 * So we are providing filter hook to validate payment fields.
518 */
519 $errors = apply_filters(
520 'fluentform/validate_payment_items_' . $paymentAction->selectedPaymentMethod,
521 [],
522 $paymentAction->getOrderItems(),
523 $paymentAction->getSubscriptionItems(),
524 $form
525 );
526
527 if ($errors) {
528 wp_send_json([
529 'errors' => $errors
530 ], 423);
531 }
532
533 $paymentAction->draftFormEntry();
534 }
535
536 public function isEnabled()
537 {
538 $paymentSettings = PaymentHelper::getPaymentSettings();
539 return $paymentSettings['status'] == 'yes';
540 }
541
542 public function modifySingleEntryLabels($labels, $submission, $form)
543 {
544 $formFields = FormFieldsParser::getPaymentFields($form);
545 if ($formFields && is_array($formFields)) {
546 $labels = ArrayHelper::except($labels, array_keys($formFields));
547 }
548 return $labels;
549 }
550
551 public function maybeAddPaymentSettings($menus, $formId)
552 {
553 $form = \FluentForm\App\Models\Form::find($formId);
554 if ($form->has_payment) {
555 $menus = array_merge(array_slice($menus, 0, 1), array(
556 'payment_settings' => [
557 'title' => __('Payment Settings', 'fluentform'),
558 'slug' => 'form_settings',
559 'hash' => 'payment_settings',
560 'route' => '/payment-settings',
561 ]
562 ), array_slice($menus, 1));
563 }
564 return $menus;
565 }
566
567
568 /**
569 * @param $html string
570 * @param $property string
571 * @param $instance ShortCodeParser
572 * @return false|string
573 */
574 public function paymentReceiptView($html, $property, $instance)
575 {
576 $entry = $instance::getEntry();
577 $receiptClass = new PaymentReceipt($entry);
578 return $receiptClass->getItem($property);
579 }
580
581 private function validateFrameLessPage($data)
582 {
583 // We should verify the transaction hash from the URL
584 $transactionHash = sanitize_text_field(ArrayHelper::get($data, 'transaction_hash'));
585 $submissionId = intval(ArrayHelper::get($data, 'fluentform_payment'));
586 if (!$submissionId) {
587 die('Validation Failed');
588 }
589
590 if ($transactionHash) {
591 $transaction = \FluentForm\App\Models\Transaction::bySubmission($submissionId)
592 ->where('transaction_hash', $transactionHash)
593 ->first();
594 if ($transaction) {
595 return true;
596 }
597
598 die('Transaction hash is invalid');
599 }
600
601 $uid = sanitize_text_field(ArrayHelper::get($data, 'entry_uid'));
602 if (!$uid) {
603 die('Validation Failed');
604 }
605
606 $originalUid = Helper::getSubmissionMeta($submissionId, '_entry_uid_hash');
607
608 if ($originalUid != $uid) {
609 die(esc_html__('Transaction UID is invalid', 'fluentform'));
610 }
611
612 return true;
613 }
614
615 public function maybeAssignTransactions($userId)
616 {
617 $user = get_user_by('ID', $userId);
618 if (!$user) {
619 return false;
620 }
621 $userEmail = $user->user_email;
622
623 $transactions = \FluentForm\App\Models\Transaction::where('payer_email', $userEmail)
624 ->where(function ($query) {
625 $query->whereNull('user_id')
626 ->orWhere('user_id', '');
627 })
628 ->get();
629
630 if (count($transactions) === 0) {
631 return false;
632 }
633
634 $submissionIds = [];
635 $transactionIds = [];
636 foreach ($transactions as $transaction) {
637 $submissionIds[] = $transaction->submission_id;
638 $transactionIds[] = $transaction->id;
639 }
640
641 $submissionIds = array_unique($submissionIds);
642 $transactionIds = array_unique($transactionIds);
643
644 // Claim only unowned submissions; payer_email is unverified and may match a registered owner.
645 \FluentForm\App\Models\Submission::whereIn('id', $submissionIds)
646 ->where(function ($query) {
647 $query->whereNull('user_id')
648 ->orWhere('user_id', '');
649 })
650 ->update([
651 'user_id' => $userId,
652 'updated_at' => current_time('mysql')
653 ]);
654
655 \FluentForm\App\Models\Transaction::whereIn('id', $transactionIds)
656 ->update([
657 'user_id' => $userId,
658 'updated_at' => current_time('mysql')
659 ]);
660
661 return true;
662 }
663
664 public function paymentTranslations($i18n)
665 {
666 $paymentI18n = array(
667 'Order Details' => __('Order Details', 'fluentform'),
668 'Product' => __('Product', 'fluentform'),
669 'Qty' => __('Qty', 'fluentform'),
670 'Unit Price' => __('Unit Price', 'fluentform'),
671 'Total' => __('Total', 'fluentform'),
672 'Sub-Total' => __('Sub-Total', 'fluentform'),
673 'Discount' => __('Discount', 'fluentform'),
674 'Price' => __('Price', 'fluentform'),
675 'Payment Details' => __('Payment Details', 'fluentform'),
676 'From Subscriptions' => __('From Subscriptions', 'fluentform'),
677 'Card Last 4' => __('Card Last 4', 'fluentform'),
678 'Payment Total' => __('Payment Total', 'fluentform'),
679 'Payment Status' => __('Payment Status', 'fluentform'),
680 'Transaction ID' => __('Transaction ID', 'fluentform'),
681 'Payment Method' => __('Payment Method', 'fluentform'),
682 'Transaction' => __('Transaction', 'fluentform'),
683 'Refunds' => __('Refunds', 'fluentform'),
684 'Refund' => __('Refund', 'fluentform'),
685 'at' => __('at', 'fluentform'),
686 'View' => __('View', 'fluentform'),
687 'has been refunded via' => __('has been refunded via', 'fluentform'),
688 'Note' => __('Note', 'fluentform'),
689 'Edit Transaction' => __('Edit Transaction', 'fluentform'),
690 'Billing Name' => __('Billing Name', 'fluentform'),
691 'Billing Email' => __('Billing Email', 'fluentform'),
692 'Billing Address' => __('Billing Address', 'fluentform'),
693 'Shipping Address' => __('Shipping Address', 'fluentform'),
694 'Reference ID' => __('Reference ID', 'fluentform'),
695 'refunds-to-be-handled-from-provider-text' => __('Please note that, Actual Refund needs to be handled in your Payment Service Provider.', 'fluentform'),
696 'Please Provide new refund amount only.' => __('Please Provide new refund amount only.', 'fluentform'),
697 'Refund Note' => __('Refund Note', 'fluentform'),
698 'Cancel' => __('Cancel', 'fluentform'),
699 'Confirm' => __('Confirm', 'fluentform'),
700 );
701 return array_merge($i18n,$paymentI18n);
702 }
703
704 public function validateSubscriptionInputs($error, $field, $formData)
705 {
706 if (isset($formData[$field['name']])) {
707 $subscriptionOptions = ArrayHelper::get($field, 'raw.settings.subscription_options', []);
708 $selectedPlanIndex = $formData[$field['name']];
709 $acceptedSubscriptionPlan = is_numeric($selectedPlanIndex) && in_array($selectedPlanIndex, array_keys($subscriptionOptions));
710 if (!$acceptedSubscriptionPlan) {
711 $error = __('This subscription plan is invalid', 'fluentform');
712 }
713 $selectedPlan = ArrayHelper::get($subscriptionOptions, $selectedPlanIndex, []);
714 if ('yes' === ArrayHelper::get($selectedPlan, 'user_input')) {
715 $userGivenValue = ArrayHelper::get($formData, "{$field['name']}_custom_$selectedPlanIndex");
716 $userGivenValue = $userGivenValue ?: 0;
717 $planMinValue = ArrayHelper::get($selectedPlan, 'user_input_min_value');
718 if (!is_numeric($userGivenValue) || $userGivenValue < 0 || ($planMinValue && $userGivenValue < $planMinValue)) {
719 $error = __('This subscription plan value is invalid', 'fluentform');
720 }
721 }
722 }
723
724 return $error;
725 }
726
727 public function validatePaymentInputs($error, $field, $formData)
728 {
729 if (ArrayHelper::get($formData, $field['name'])) {
730 $fieldType = ArrayHelper::get($field, 'raw.attributes.type');
731
732 if (in_array($fieldType, ['radio', 'select', 'checkbox'])) {
733 $pricingOptions = array_column(
734 ArrayHelper::get($field, 'raw.settings.pricing_options', []),
735 'label'
736 );
737
738 $pricingOptions = array_map('sanitize_text_field', $pricingOptions);
739
740 if (in_array($fieldType, ['radio', 'select'])) {
741 $acceptedPaymentPlan = in_array($formData[$field['name']], $pricingOptions);
742 } else {
743 $acceptedPaymentPlan = array_diff($formData[$field['name']], $pricingOptions);
744
745 $acceptedPaymentPlan = empty($acceptedPaymentPlan);
746 }
747
748 if (!$acceptedPaymentPlan) {
749 $error = __('This payment item is invalid', 'fluentform');
750 }
751 }
752 }
753
754 return $error;
755 }
756
757 public function validatePaymentMethod($error, $field, $formData, $fields, $form)
758 {
759 if ($selectedMethod = ArrayHelper::get($formData, $field['name'])) {
760 $activeMethods = array_keys(PaymentHelper::getFormPaymentMethods($form->id));
761 if (!in_array($selectedMethod, $activeMethods)) {
762 $error = __('This payment method is invalid', 'fluentform');
763 }
764 }
765 return $error;
766 }
767 }
768