PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.2
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.2
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 / PaymentMethods / Stripe / StripeInlineProcessor.php

StripeInlineProcessor.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.2, at app/Modules/Payments/PaymentMethods/Stripe/StripeInlineProcessor.php

634 lines 25.5 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\PaymentMethods\Stripe;
4
5 use FluentForm\App\Helpers\Helper;
6 use FluentForm\App\Modules\Payments\PaymentHelper;
7 use FluentForm\Framework\Helpers\ArrayHelper;
8 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\SCA;
9 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Plan;
10 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Invoice;
11 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Customer;
12
13 if (!defined('ABSPATH')) {
14 exit; // Exit if accessed directly.
15 }
16
17 class StripeInlineProcessor extends StripeProcessor
18 {
19
20 public function init()
21 {
22 /*
23 * After form submission this hooks fire to start Making payment
24 */
25 add_action('fluentform/process_payment_stripe_inline', [$this, 'handlePaymentAction'], 10, 6);
26
27 /*
28 * Mainly for single payment items
29 */
30 add_action('wp_ajax_fluentform_sca_inline_confirm_payment', [$this, 'confirmScaPayment']);
31 add_action('wp_ajax_nopriv_fluentform_sca_inline_confirm_payment', [$this, 'confirmScaPayment']);
32
33 /*
34 * For Subscription payment + maybe single payment items
35 */
36 add_action('wp_ajax_fluentform_sca_inline_confirm_payment_setup_intents', array($this, 'confirmScaSetupIntentsPayment'));
37 add_action('wp_ajax_nopriv_fluentform_sca_inline_confirm_payment_setup_intents', array($this, 'confirmScaSetupIntentsPayment'));
38 }
39
40 public function handlePaymentAction($submissionId, $submissionData, $form, $methodSettings, $hasSubscriptions, $totalPayable)
41 {
42 $this->setSubmissionId($submissionId);
43 $this->form = $form;
44 $submission = $this->getSubmission();
45 $paymentTotal = $this->getAmountTotal();
46
47 if (!$paymentTotal && !$hasSubscriptions) {
48 return false;
49 }
50
51 // Create the initial transaction here
52 $transaction = $this->createInitialPendingTransaction($submission, $hasSubscriptions);
53
54 $paymentMethodId = ArrayHelper::get($submissionData['response'], '__stripe_payment_method_id');
55 $customerArgs = $this->customerArguments($paymentMethodId, $submission);
56
57 $customer = Customer::createCustomer($customerArgs, $this->form->id);
58
59 if (is_wp_error($customer)) {
60 // We have errors
61 $this->handlePaymentChargeError($customer->get_error_message(), $submission, $transaction);
62 }
63
64 if ($transaction->transaction_type == 'subscription') {
65 $this->handleSetupIntent($submission, $paymentMethodId, $customer, $transaction, $totalPayable);
66 } else {
67 // Let's create the one time payment first
68 // We will handle One-Time Payment Here only
69 $paymentSettings = PaymentHelper::getFormSettings($form->id, 'admin');
70 $intentArgs = [
71 'payment_method' => $paymentMethodId,
72 'amount' => $transaction->payment_total,
73 'currency' => $transaction->currency,
74 'confirmation_method' => 'manual',
75 'confirm' => 'true',
76 'description' => $this->getProductNames(),
77 'statement_descriptor_suffix' => StripeSettings::getPaymentDescriptor($form),
78 'metadata' => $this->getIntentMetaData($submission, $form, $transaction, $paymentSettings),
79 'customer' => $customer->id
80 ];
81
82 $intentArgs = apply_filters('fluentform/stripe_checkout_args_inline', $intentArgs, $submission, $transaction, $form);
83
84 // If FluentForm Pro is not installed, apply the fee 1.9% of the total amount
85 if (!Helper::hasPro()) {
86 $applicationFeeAmount = (int) ($totalPayable * 0.019);
87 $intentArgs['application_fee_amount'] = $applicationFeeAmount;
88 }
89 $this->handlePaymentIntent($transaction, $submission, $intentArgs);
90 }
91 }
92
93 // This is only for Subscription Payment
94 protected function handleSetupIntent($submission, $paymentMethodId, $customer, $transaction, $totalPayable)
95 {
96 if (is_wp_error($customer)) {
97 $this->handlePaymentChargeError($customer->get_error_message(), $submission, $transaction, false, 'customer');
98 }
99
100 $subscriptions = $this->getSubscriptions();
101
102 $subscription = $subscriptions[0];
103
104 $subscriptionTransactionArgs = Plan::getPriceIdsFromSubscriptionTransaction($subscription, $transaction);
105
106 if(is_wp_error($subscriptionTransactionArgs)) {
107 $this->handlePaymentChargeError($customer->get_error_message(), $submission, $transaction, false, 'customer');
108 }
109
110 $subscriptionArgs = [
111 'customer' => $customer->id,
112 'metadata' => $this->getIntentMetaData($submission, $this->getForm(), $transaction),
113 'payment_behavior' => 'allow_incomplete',
114 ];
115
116 $subscriptionArgs['items'] = $subscriptionTransactionArgs['items'];
117
118 if ($signupFee = $subscriptionTransactionArgs['signup_fee']) {
119 Invoice::createItem([
120 'amount' => $signupFee,
121 'currency' => $submission->currency,
122 'customer' => $customer->id,
123 /* translators: %s is the plan name */
124 'description' => sprintf(__('Signup fee for %s', 'fluentform'), $subscription->plan_name),
125 ], $submission->form_id);
126 }
127
128 // Maybe we have to set a cancel_at parameter to subscription args
129 if ($cancelledAt = Plan::getCancelledAtTimestamp($subscription)) {
130 $subscriptionArgs['cancel_at'] = $cancelledAt;
131 }
132
133 if ($subscription->trial_days) {
134 $dateTime = current_datetime();
135 $localtime = $dateTime->getTimestamp() + $dateTime->getOffset();
136 $subscriptionArgs['trial_end'] = $localtime + $subscription->trial_days * 86400;
137 }
138
139 $subscriptionArgs = apply_filters('fluentform/stripe_subscription_args_inline', $subscriptionArgs, $submission, $transaction, $this->getForm());
140
141 // If FluentForm Pro is not installed, apply the fee 1.9%
142 if (!Helper::hasPro()) {
143 $subscriptionArgs['application_fee_percent'] = 1.9;
144 }
145
146 $subscriptionPayment = Plan::subscribe($subscriptionArgs, $submission->form_id);
147
148 if (is_wp_error($subscriptionPayment)) {
149 $this->handlePaymentChargeError($subscriptionPayment->get_error_message(), $submission, $transaction, false, 'subscription');
150 }
151
152 $invoice = Invoice::retrieve(
153 $subscriptionPayment->latest_invoice,
154 $this->form->id,
155 [
156 'expand' => ['payment_intent.charges']
157 ]
158 );
159 if (is_wp_error($invoice)) {
160 $this->handlePaymentChargeError($invoice->get_error_message(), $submission, $transaction, false, 'invoice');
161 }
162
163 if (
164 $invoice->payment_intent &&
165 $invoice->payment_intent->status == 'requires_action' &&
166 $invoice->payment_intent->next_action->type == 'use_stripe_sdk'
167 ) {
168 $transactionId = false;
169 if ($transaction) {
170 $transactionId = $transaction->id;
171 }
172 $this->processScaBeforeVerification($submission->form_id, $submission->id, $transactionId, $invoice->payment_intent->id);
173
174 $nonceAction = 'fluentform_sca_confirm_' . $submission->id;
175 $nonce = wp_create_nonce($nonceAction);
176
177 wp_send_json_success([
178 'nextAction' => 'payment',
179 'actionName' => 'stripeSetupIntent',
180 'stripe_subscription_id' => $subscriptionPayment->id,
181 'payment_method_id' => $paymentMethodId,
182 'intent' => $invoice->payment_intent,
183 'submission_id' => $submission->id,
184 'customer_name' => ($transaction) ? $transaction->payer_name : '',
185 'customer_email' => ($transaction) ? $transaction->payer_email : '',
186 'client_secret' => $invoice->payment_intent->client_secret,
187 '_ff_stripe_nonce' => $nonce,
188 'message' => __('Verifying your card details. Please wait...', 'fluentform'),
189 'result' => [
190 'insert_id' => $submission->id
191 ]
192 ], 200);
193 }
194
195 // now this payment is successful. We don't need anything else
196 $this->handlePaidSubscriptionInvoice($invoice, $submission);
197 }
198
199 protected function customerArguments($paymentMethodId, $submission)
200 {
201 $customerArgs = [
202 'payment_method' => $paymentMethodId,
203 'invoice_settings' => [
204 'default_payment_method' => $paymentMethodId
205 ],
206 'metadata' => [
207 'submission_id' => $submission->id,
208 'form_id' => $submission->form_id,
209 'form_name' => wp_strip_all_tags($this->form->title)
210 ]
211 ];
212
213 $receiptEmail = PaymentHelper::getCustomerEmail($submission, $this->form);
214
215 if ($receiptEmail) {
216 $customerArgs['email'] = $receiptEmail;
217 }
218
219 $receiptName = PaymentHelper::getCustomerName($submission, $this->form);
220
221 if ($receiptName) {
222 $customerArgs['name'] = $receiptName;
223 $customerArgs['description'] = $receiptName;
224 }
225
226 $address = PaymentHelper::getCustomerAddress($submission);
227 if ($address) {
228 $customerArgs['address'] = [
229 'city' => ArrayHelper::get($address, 'city'),
230 'country' => ArrayHelper::get($address, 'country'),
231 'line1' => ArrayHelper::get($address, 'address_line_1'),
232 'line2' => ArrayHelper::get($address, 'address_line_2'),
233 'postal_code' => ArrayHelper::get($address, 'zip'),
234 'state' => ArrayHelper::get($address, 'state'),
235 ];
236 }
237
238 return $customerArgs;
239 }
240
241 protected function handlePaidSubscriptionInvoice($invoice, $submission)
242 {
243 if ($invoice->status !== 'paid') {
244 wp_send_json([
245 'errors' => __('Stripe Error: Payment Failed! Please try again.', 'fluentform')
246 ], 423);
247 }
248
249 // Submission status as paid
250 $this->changeSubmissionPaymentStatus('paid');
251
252 $subscriptions = $this->getSubscriptions();
253
254 $this->processSubscriptionSuccess($subscriptions, $invoice, $submission);
255
256 $transaction = $this->getLastTransaction($submission->id);
257
258 $paymentStatus = $this->getIntentSuccessName($invoice->payment_intent);
259 $this->processOnetimeSuccess($invoice, $transaction, $paymentStatus);
260
261 $this->recalculatePaidTotal();
262
263 $this->sendSuccess($submission);
264 }
265
266 protected function handlePaymentIntent($transaction, $submission, $intentArgs)
267 {
268 $formSettings = PaymentHelper::getFormSettings($submission->form_id);
269
270 if (PaymentHelper::isZeroDecimal($transaction->currency)) {
271 $intentArgs['amount'] = intval($transaction->payment_total / 100);
272 }
273
274 $receiptEmail = PaymentHelper::getCustomerEmail($submission, $this->form);
275
276 if ($receiptEmail && ArrayHelper::get($formSettings, 'disable_stripe_payment_receipt') != 'yes') {
277 $intentArgs['receipt_email'] = $receiptEmail;
278 }
279
280 $intent = SCA::createPaymentIntent($intentArgs, $this->form->id);
281
282 if (is_wp_error($intent)) {
283 $this->handlePaymentChargeError($intent->get_error_message(), $submission, $transaction, false, 'payment_intent');
284 }
285
286 if (
287 $intent->status == 'requires_action' &&
288 $intent->next_action &&
289 $intent->next_action->type == 'use_stripe_sdk'
290 ) {
291 $this->processScaBeforeVerification($submission->form_id, $submission->id, $transaction->id, $intent->id);
292
293 // Generate nonce for secure SCA confirmation
294 $nonceAction = 'fluentform_sca_confirm_' . $submission->id;
295 $nonce = wp_create_nonce($nonceAction);
296
297 # Tell the client to handle the action
298 wp_send_json_success([
299 'nextAction' => 'payment',
300 'actionName' => 'initStripeSCAModal',
301 'submission_id' => $submission->id,
302 'client_secret' => $intent->client_secret,
303 '_ff_stripe_nonce' => $nonce,
304 'message' => apply_filters('fluentform/stripe_strong_customer_verify_waiting_message', __('Verifying strong customer authentication. Please wait...', 'fluentform')),
305 'result' => [
306 'insert_id' => $submission->id
307 ]
308 ], 200);
309
310 } else if ($intent->status == 'succeeded') {
311 // Payment is succeeded here
312 $charge = $intent->charges->data[0];
313
314 $this->handlePaymentSuccess($charge, $transaction, $submission);
315 } else {
316 $message = __('Payment Failed! Your card may have been declined.', 'fluentform');
317
318 if (!empty($intent->error->message)) {
319 $message = $intent->error->message;
320 }
321
322 $this->handlePaymentChargeError($message, $submission, $transaction, false, 'payment_intent');
323 }
324 }
325
326 protected function handlePaymentSuccess($charge, $transaction, $submission)
327 {
328 $transactionData = [
329 'charge_id' => $charge->payment_intent,
330 'payment_method' => 'stripe',
331 'payment_mode' => $this->getPaymentMode(),
332 'payment_note' => maybe_serialize($charge)
333 ];
334
335 $methodDetails = $charge->payment_method_details;
336 if ($methodDetails && !empty($methodDetails->card)) {
337 $transactionData['card_brand'] = $methodDetails->card->brand;
338 $transactionData['card_last_4'] = $methodDetails->card->last4;
339 }
340
341 $this->updateTransaction($transaction->id, $transactionData);
342
343 $this->changeTransactionStatus($transaction->id, 'paid');
344
345 $logData = [
346 'parent_source_id' => $submission->form_id,
347 'source_type' => 'submission_item',
348 'source_id' => $submission->id,
349 'component' => 'Payment',
350 'status' => 'info',
351 'title' => __('Payment Status changed', 'fluentform'),
352 'description' => __('Payment status changed to paid', 'fluentform')
353 ];
354
355 do_action('fluentform/log_data', $logData);
356
357 $this->updateSubmission($submission->id, [
358 'payment_status' => 'paid',
359 'payment_method' => 'stripe',
360 ]);
361
362 $logData = [
363 'parent_source_id' => $submission->form_id,
364 'source_type' => 'submission_item',
365 'source_id' => $submission->id,
366 'component' => 'Payment',
367 'status' => 'success',
368 'title' => __('Payment Complete', 'fluentform'),
369 'description' => __('One time Payment Successfully made via Stripe. Charge ID: ', 'fluentform') . $charge->id
370 ];
371
372 do_action('fluentform/log_data', $logData);
373
374 $this->recalculatePaidTotal();
375
376 $this->sendSuccess($submission);
377 }
378
379 /**
380 * Validate SCA payment confirmation request
381 *
382 * @param int $submissionId Submission ID
383 * @param string $paymentIntentId Payment Intent ID
384 * @param object|null $submission Submission object
385 * @param object|null $transaction Transaction object
386 * @return array|WP_Error Array with validation result or WP_Error on strict mode failure
387 */
388 protected function validateScaRequest($submissionId, $paymentIntentId, $submission = null, $transaction = null)
389 {
390 $warnings = [];
391
392 // Validate nonce — always required by default.
393 // Filter allows opt-out only for backward compat; emits deprecation notice.
394 $nonce = isset($_REQUEST['_ff_stripe_nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['_ff_stripe_nonce'])) : '';
395
396 if ($nonce) {
397 $nonceAction = 'fluentform_sca_confirm_' . $submissionId;
398 if (!wp_verify_nonce($nonce, $nonceAction)) {
399 return new \WP_Error('invalid_nonce', __('Security verification failed. Invalid nonce.', 'fluentform'));
400 }
401 } else {
402 $strictMode = apply_filters('fluentform/stripe_sca_strict_security', true);
403 if ($strictMode) {
404 return new \WP_Error('missing_nonce', __('Security verification failed. Nonce required.', 'fluentform'));
405 }
406 _deprecated_argument(
407 'fluentform/stripe_sca_strict_security',
408 '6.2.0',
409 esc_html(__('Disabling strict SCA nonce verification is deprecated and will be removed in a future version.', 'fluentform'))
410 );
411 $warnings[] = 'No nonce provided for SCA payment confirmation';
412 }
413
414 // Validate submission exists
415 if (!$submission || !$submission->id) {
416 return new \WP_Error('invalid_submission', __('Invalid submission.', 'fluentform'));
417 }
418
419
420 if ($submission->payment_status === 'paid') {
421 return new \WP_Error(
422 'already_paid',
423 __('This payment has already been completed and cannot be modified.', 'fluentform')
424 );
425 }
426
427 // Transaction must exist and be in 'intended' status (set by processScaBeforeVerification
428 // when the SCA flow starts). A 'pending' transaction means SCA was never initiated,
429 // 'paid'/'failed' means it's already been processed.
430 if (!$transaction) {
431 return new \WP_Error('no_transaction', __('No transaction found for this submission.', 'fluentform'));
432 }
433
434 if ($transaction->status !== 'intended') {
435 return new \WP_Error(
436 'invalid_transaction_status',
437 __('This transaction is not awaiting payment confirmation.', 'fluentform')
438 );
439 }
440
441 // Verify the payment intent ID matches what was stored during SCA initiation.
442 // processScaBeforeVerification() stores the intent as charge_id.
443 if ($transaction->charge_id && $transaction->charge_id !== $paymentIntentId) {
444 return new \WP_Error(
445 'payment_intent_mismatch',
446 __('Payment verification failed. Payment intent does not match.', 'fluentform')
447 );
448 }
449
450 // Log warnings for monitoring
451 if (!empty($warnings) && defined('WP_DEBUG') && WP_DEBUG) {
452 $logData = [
453 'parent_source_id' => $submission->form_id,
454 'source_type' => 'submission_item',
455 'source_id' => $submission->id,
456 'component' => 'Payment',
457 'status' => 'warning',
458 'title' => __('Stripe SCA Security Warning', 'fluentform'),
459 'description' => implode('; ', $warnings)
460 ];
461 do_action('fluentform/log_data', $logData);
462 }
463
464 return [
465 'valid' => true,
466 'warnings' => $warnings
467 ];
468 }
469
470 public function confirmScaPayment()
471 {
472 $submissionId = isset($_REQUEST['submission_id']) ? (int)$_REQUEST['submission_id'] : 0;
473 $paymentMethod = isset($_REQUEST['payment_method']) ? sanitize_text_field(wp_unslash($_REQUEST['payment_method'])) : '';
474 $paymentIntentId = isset($_REQUEST['payment_intent_id']) ? sanitize_text_field(wp_unslash($_REQUEST['payment_intent_id'])) : '';
475
476 $this->setSubmissionId($submissionId);
477 $submission = $this->getSubmission();
478 $this->form = $this->getForm();
479
480 $transaction = $this->getLastTransaction($submissionId);
481
482 $validation = $this->validateScaRequest($submissionId, $paymentIntentId, $submission, $transaction);
483
484 if (is_wp_error($validation)) {
485 wp_send_json([
486 'errors' => $validation->get_error_message()
487 ], 423);
488 }
489
490 // Use submission's form_id rather than trusting $_REQUEST
491 $formId = $submission->form_id;
492
493 $confirmation = SCA::confirmPayment($paymentIntentId, [
494 'payment_method' => $paymentMethod
495 ], $formId);
496
497 if (is_wp_error($confirmation)) {
498 $message = 'Payment has been failed. ' . $confirmation->get_error_message();
499 $this->handlePaymentChargeError($message, $submission, $transaction, $confirmation, 'payment_error');
500 }
501
502 if ($confirmation->status == 'succeeded') {
503 $charge = $confirmation->charges->data[0];
504
505 // Verify the confirmed amount matches the transaction amount.
506 // Normalize for zero-decimal currencies: FluentForm stores amounts x100 internally,
507 // but Stripe returns amounts in the currency's smallest unit (e.g. yen for JPY).
508 $confirmedAmount = (int) $confirmation->amount;
509 if (PaymentHelper::isZeroDecimal($transaction->currency)) {
510 $confirmedAmount = $confirmedAmount * 100;
511 }
512 if ($transaction->payment_total && $confirmedAmount != intval($transaction->payment_total)) {
513 $logData = [
514 'parent_source_id' => $submission->form_id,
515 'source_type' => 'submission_item',
516 'source_id' => $submission->id,
517 'component' => 'Payment',
518 'status' => 'error',
519 'title' => __('Stripe Amount Mismatch', 'fluentform'),
520 'description' => sprintf(
521 // translators: %1$d is the expected amount, %2$d is the confirmed amount
522 __('Expected %1$d but Stripe confirmed %2$d. Payment rejected.', 'fluentform'),
523 intval($transaction->payment_total),
524 intval($confirmation->amount)
525 )
526 ];
527 do_action('fluentform/log_data', $logData);
528
529 wp_send_json([
530 'errors' => __('Payment amount verification failed.', 'fluentform')
531 ], 423);
532 }
533
534 $this->handlePaymentSuccess($charge, $transaction, $submission);
535 } else {
536 $this->handlePaymentChargeError('We could not verify your payment. Please try again', $submission, $transaction, $confirmation, 'payment_error');
537 }
538 }
539
540 public function confirmScaSetupIntentsPayment()
541 {
542 $submissionId = isset($_REQUEST['submission_id']) ? intval($_REQUEST['submission_id']) : 0;
543 $intentId = isset($_REQUEST['payment_intent_id']) ? sanitize_text_field(wp_unslash($_REQUEST['payment_intent_id'])) : '';
544
545 $this->setSubmissionId($submissionId);
546 $this->form = $this->getForm();
547
548 $submission = $this->getSubmission();
549 $transaction = $this->getLastTransaction($submissionId);
550
551 // Validate the request
552 $validation = $this->validateScaRequest($submissionId, $intentId, $submission, $transaction);
553
554 if (is_wp_error($validation)) {
555 wp_send_json([
556 'errors' => $validation->get_error_message()
557 ], 423);
558 }
559
560 // Use submission's form_id rather than trusting $_REQUEST
561 $formId = $submission->form_id;
562
563 // Let's retrieve the intent
564 $intent = SCA::retrievePaymentIntent($intentId, [
565 'expand' => [
566 'invoice.payment_intent'
567 ]
568 ], $formId);
569
570 if (is_wp_error($intent)) {
571 $this->handlePaymentChargeError($intent->get_error_message(), $submission, false, false, 'payment_intent');
572 }
573
574 $invoice = $intent->invoice;
575
576 $this->handlePaidSubscriptionInvoice($invoice, $submission);
577 }
578
579 protected function sendSuccess($submission)
580 {
581 try {
582 $returnData = $this->getReturnData();
583 wp_send_json_success($returnData, 200);
584
585 } catch (\Exception $e) {
586 wp_send_json([
587 'errors' => $e->getMessage()
588 ], 423);
589 }
590
591 }
592
593 protected function processScaBeforeVerification($formId, $submissionId, $transactionId, $chargeId)
594 {
595 if ($transactionId) {
596 $this->updateTransaction($transactionId, [
597 'charge_id' => $chargeId,
598 'payment_mode' => $this->getPaymentMode()
599 ]);
600
601 $this->changeTransactionStatus($transactionId, 'intended');
602 }
603
604 $logData = [
605 'parent_source_id' => $formId,
606 'source_type' => 'submission_item',
607 'source_id' => $submissionId,
608 'component' => 'Payment',
609 'status' => 'info',
610 'title' => __('Stripe SCA Required', 'fluentform'),
611 'description' => __('SCA is required for this payment. Requested SCA info from customer', 'fluentform')
612 ];
613
614 do_action('fluentform/log_data', $logData);
615 }
616
617 /**
618 * Products name comma separated
619 * @return string
620 */
621 public function getProductNames()
622 {
623 $orderItems = $this->getOrderItems();
624 $itemsHtml = '';
625 foreach ($orderItems as $item) {
626 $itemsHtml != "" && $itemsHtml .= ", ";
627 $itemsHtml .= $item->item_name ;
628 }
629
630 return $itemsHtml;
631 }
632
633 }
634