PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.7
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.7
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 / TransactionShortcodes.php

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

535 lines 20.8 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 use FluentForm\App\Helpers\Helper;
6 use FluentForm\App\Models\Subscription;
7 use FluentForm\App\Models\Transaction;
8 use FluentForm\App\Modules\Acl\Acl;
9 use FluentForm\Framework\Helpers\ArrayHelper;
10 use FluentForm\App\Modules\Payments\Classes\PaymentReceipt;
11 use FluentForm\App\Modules\Payments\Orders\OrderData;
12
13 if (!defined('ABSPATH')) {
14 exit; // Exit if accessed directly.
15 }
16
17 class TransactionShortcodes
18 {
19 public function init()
20 {
21 add_shortcode('fluentform_payments', array($this, 'registerPaymentsShortcode'));
22 add_shortcode('fluentform_payment_view', array($this, 'registerReceiptShortcode'));
23 add_action('wp_ajax_fluentform_user_payment_endpoints', array($this, 'routeAjaxEndpoints'));
24 add_action('fluentform/payment_view_payment', array($this, 'renderPaymentReceiptPage'));
25 }
26
27 public function registerPaymentsShortcode($atts, $content = '')
28 {
29 $userId = get_current_user_id();
30 if (!$userId) {
31 if (!$content) {
32 return '';
33 }
34 return '<div class="ff_not_login">' . $content . '</div>';
35 }
36
37 $atts = shortcode_atts([
38 'type' => 'all',
39 'payment_statuses' => '',
40 'subscription_statuses' => '',
41 ], $atts);
42
43 if ($atts['payment_statuses']) {
44 $pay_statuses_array = array_filter(explode(',', $atts['payment_statuses']));
45 $atts['payment_statuses'] = $pay_statuses_array;
46 }
47
48 if ($atts['subscription_statuses']) {
49 $sub_statuses_array = array_filter(explode(',', $atts['subscription_statuses']));
50 $atts['subscription_statuses'] = $sub_statuses_array;
51 }
52
53 $viewConfig = $this->getViewConfig();
54
55 $html = '';
56
57 if ($atts['type'] == 'all' || $atts['type'] == 'subscriptions') {
58 $subscriptionsHtml = $this->getSubscriptionsHtml($userId, $viewConfig, $atts);
59 if ($subscriptionsHtml) {
60 wp_enqueue_script('fluentform_transactions', fluentFormMix('js/fluentform_transactions_ui.js'), ['jquery'], FLUENTFORM_VERSION, true);
61
62 wp_enqueue_style(
63 'fluentform_transactions',
64 fluentFormMix('css/fluentform_transactions.css'),
65 [],
66 FLUENTFORM_VERSION
67 );
68
69 wp_localize_script('fluentform_transactions', 'ff_transactions_vars', [
70 'ajax_url' => admin_url('admin-ajax.php'),
71 'nonce' => wp_create_nonce('fluentform_transactions')
72 ]);
73
74 if ($atts['type'] == 'subscriptions') {
75 $html .= $subscriptionsHtml;
76 return $html;
77 } else {
78 $withTitleHtml = '<div class="ff_transactions_wrapper">';
79 if (!empty($viewConfig['transactions_title'])) {
80 $withTitleHtml .= '<h3>' . $viewConfig['subscriptions_title'] . '</h3>';
81 }
82 $html .= $withTitleHtml . $subscriptionsHtml . '</div>';
83 }
84 } else if ($atts['type'] == 'subscriptions') {
85 return '<p class="ff_no_sub">' . __('No subscription payments found', 'fluentform') . '</p>';
86 }
87 }
88
89 $html .= $this->getTransactionsHtml($userId, $viewConfig, $atts);
90
91 return $html;
92 }
93
94 public function registerReceiptShortcode($atts, $content = '')
95 {
96 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a public shortcode for displaying receipt
97 $data = [
98 'transaction' => isset($_REQUEST['transaction']) ? sanitize_text_field(wp_unslash($_REQUEST['transaction'])) : '',
99 ];
100 return $this->renderPaymentReceiptPage($data, false);
101 }
102
103 public function renderPaymentReceiptPage($data, $echo = true)
104 {
105 $transactionHash = ArrayHelper::get($data, 'transaction');
106
107 $transaction = fluentFormApi('submissions')->transaction($transactionHash, 'transaction_hash');
108
109 if(!$transaction) {
110 if($echo) {
111 status_header(200);
112 echo esc_html__('Sorry no transaction found', 'fluentform');
113 exit(200);
114 }
115 return '';
116 }
117
118 $submission = fluentFormApi('submissions')->find($transaction->submission_id);
119
120 if ($transaction->transaction_type == 'subscription') {
121 $transaction->subscription = fluentFormApi('submissions')->getSubscription($transaction->subscription_id);
122 }
123
124 $transaction->form = fluentFormApi('forms')->find($transaction->form_id);
125 $transaction->submission = $submission;
126
127 $transactionHtml = $this->getTransactionHtml($transaction);
128
129 if(!$echo) {
130 return $transactionHtml;
131 }
132
133 add_action('wp_enqueue_scripts', function () {
134 wp_enqueue_style('fluent-form-landing', fluentFormMix('css/frameless.css'), [], FLUENTFORM_VERSION);
135 });
136
137 $form = fluentFormApi('forms')->find($transaction->form_id);
138
139 $data = [
140 'form' => $form,
141 'status' => $transaction->status,
142 'title' => 'Transaction #'.$transaction->id,
143 'message' => $transactionHtml
144 ];
145
146 add_filter('pre_get_document_title', function ($title) use ($data) {
147 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- document_title_separator is a WordPress core hook
148 return $data['title'] . ' ' . apply_filters('document_title_separator', '-') . ' ' . $data['form']->title;
149 });
150
151 status_header(200);
152 $file = FLUENTFORM_DIR_PATH . 'app/Views/frameless/frameless_page_view.php';
153 extract($data);
154 ob_start();
155 include($file);
156 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Complete HTML template output with pre-sanitized content from view file
157 echo ob_get_clean();
158 exit(200);
159 }
160
161 public function routeAjaxEndpoints()
162 {
163 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified by verifyNonce() method below
164 $route = sanitize_text_field(ArrayHelper::get($_REQUEST, 'route'));
165 $this->verifyNonce();
166 if ($route == 'get_subscription_transactions') {
167 $this->sendSubscriptionPayments();
168 } else if ($route == 'cancel_transaction') {
169 $this->cancelSubscriptionAjax();
170 }
171 }
172
173 public function getSubscriptionsHtml($userId, $viewConfig = [], $atts = [])
174 {
175 if (!$viewConfig) {
176 $viewConfig = $this->getViewConfig();
177 }
178
179 $subscriptions = fluentFormApi('submissions')->subscriptionsByUserId($userId, [
180 'form_title' => true,
181 'statuses' => ArrayHelper::get($atts, 'subscription_statuses', [])
182 ]);
183
184 if (!count($subscriptions)) {
185 return '';
186 }
187
188 foreach ($subscriptions as $subscription) {
189 $subscription->formatted_recurring_amount = PaymentHelper::formatMoney($subscription->recurring_amount, $subscription->currency);
190 $billingText = '';
191
192 if ($subscription->status == 'active') {
193 if ($subscription->bill_times) {
194 /* translators: 1: number of payments */
195 $billingText = sprintf(esc_html__('Will be cancelled after %d payments', 'fluentform'), $subscription->bill_times);
196 } else {
197 $billingText = __('will be billed until cancelled', 'fluentform');
198 }
199 }
200
201 $subscription->billing_text = $billingText;
202 $subscription->starting_date_formated = date_i18n($viewConfig['date_format'], strtotime($subscription->created_at));
203 $subscription->can_cancel = $this->canCancelSubscription($subscription);
204 $subscription->status = ArrayHelper::get(PaymentHelper::getSubscriptionStatuses(), $subscription->status, $subscription->status);
205 $subscription->billing_interval = ArrayHelper::get(PaymentHelper::getBillingIntervals(), $subscription->billing_interval, $subscription->billing_interval);
206 }
207
208 return PaymentHelper::loadView('user_subscriptions_table', [
209 'subscriptions' => $subscriptions,
210 'config' => $viewConfig
211 ]);
212 }
213
214 public function getTransactionsHtml($userId, $viewConfig = [], $atts = [])
215 {
216 if (!$viewConfig) {
217 $viewConfig = $this->getViewConfig();
218 }
219
220 $transactions = fluentFormApi('submissions')->transactionsByUserId($userId, [
221 'transaction_types' => ['onetime'],
222 'statuses' => ArrayHelper::get($atts, 'payment_statuses', [])
223 ]);
224
225 if (!count($transactions)) {
226 return '';
227 }
228
229
230 foreach ($transactions as $transaction) {
231 $transaction->formatted_amount = PaymentHelper::formatMoney($transaction->payment_total, $transaction->currency);
232 $transaction->formatted_date = date_i18n($viewConfig['date_time_format'], strtotime($transaction->created_at));
233
234 if (!$transaction->transaction_hash) {
235 $hash = md5(wp_generate_uuid4() . wp_rand(0, 1000));
236 Transaction::where('id', $transaction->id)
237 ->update(['transaction_hash' => $hash]);
238 $transaction->transaction_hash = $hash;
239 }
240
241 $transaction->view_url = $viewConfig['base_url'] . 'transaction=' . $transaction->transaction_hash . '&payment_method=' . $transaction->payment_method;
242 $transaction->status = ArrayHelper::get(PaymentHelper::getPaymentStatuses(), $transaction->status, $transaction->status);
243
244 }
245
246 $transactionsHtml = PaymentHelper::loadView('transactions_table', [
247 'transactions' => $transactions,
248 'config' => $viewConfig
249 ]);
250
251 $html = '<div class="ff_transactions_wrapper">';
252 if (!empty($viewConfig['transactions_title'])) {
253 $html .= '<h3>' . $viewConfig['transactions_title'] . '</h3>';
254 }
255
256 return $html . $transactionsHtml . '</div>';
257 }
258
259 public function sendSubscriptionPayments()
260 {
261 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified by verifyNonce() in routeAjaxEndpoints()
262 $subscriptionId = intval(ArrayHelper::get($_REQUEST, 'subscription_id'));
263
264 if (!$subscriptionId) {
265 wp_send_json_error([
266 'message' => __('Invalid Subscription ID', 'fluentform'),
267 ], 423);
268 }
269
270 $userId = get_current_user_id();
271
272 $subscription = Subscription::select(['fluentform_subscriptions.*', 'fluentform_submissions.user_id'])
273 ->where('fluentform_submissions.user_id', $userId)
274 ->where('fluentform_subscriptions.id', $subscriptionId)
275 ->join('fluentform_submissions', 'fluentform_submissions.id', '=', 'fluentform_subscriptions.submission_id')
276 ->first();
277
278 if (!$subscription) {
279 wp_send_json_error([
280 'message' => __('Invalid Subscription ID', 'fluentform'),
281 ], 423);
282 }
283
284 $transactions = fluentFormApi('submissions')->transactionsBySubscriptionId($subscription->id);
285
286 if (!count($transactions)) {
287 wp_send_json_error([
288 'message' => __('Sorry, no related payments found', 'fluentform'),
289 ], 423);
290 }
291
292 $viewConfig = $this->getViewConfig();
293
294 foreach ($transactions as $transaction) {
295 if (!$transaction->transaction_hash) {
296 $hash = md5(wp_generate_uuid4() . wp_rand(0, 1000));
297 Transaction::where('id', $transaction->id)
298 ->update(['transaction_hash' => $hash]);
299 $transaction->transaction_hash = $hash;
300 }
301
302 $transaction->formatted_amount = PaymentHelper::formatMoney($transaction->payment_total, $transaction->currency);
303 $transaction->formatted_date = date_i18n($viewConfig['date_time_format'], strtotime($transaction->created_at));
304 $transaction->view_url = false; //$viewConfig['base_url'] . 'transaction=' . $transaction->transaction_hash . '&payment_method=' . $transaction->payment_method;
305 $transaction->status = ArrayHelper::get(PaymentHelper::getPaymentStatuses(), $transaction->status, $transaction->status);
306
307 }
308
309 $viewConfig['has_view_action'] = false;
310
311 $html = PaymentHelper::loadView('user_subscription_payments', [
312 'transactions' => $transactions,
313 'config' => $viewConfig
314 ]);
315
316 $html = '<h4>' . __('Related Payments', 'fluentform') . '</h4>' . $html;
317
318 wp_send_json_success([
319 'html' => $html
320 ]);
321
322 }
323
324 private function getViewConfig()
325 {
326 $paymentSettings = PaymentHelper::getPaymentSettings();
327 $pageId = ArrayHelper::get($paymentSettings, 'receipt_page_id');
328
329 $urlBase = false;
330 if($pageId) {
331 $urlBase = get_permalink($pageId);
332 }
333
334 if(!$urlBase) {
335 $urlBase = add_query_arg([
336 'fluentform_payment' => 'view',
337 'route' => 'payment'
338 ], Helper::getFrontendFacingUrl('index.php'));
339 }
340
341 $urlBase = apply_filters_deprecated(
342 'fluentform_transaction_view_url',
343 [
344 $urlBase
345 ],
346 FLUENTFORM_FRAMEWORK_UPGRADE,
347 'fluentform/transaction_view_url',
348 'Use fluentform/transaction_view_url instead of fluentform_transaction_view_url.'
349 );
350
351 $urlBase = apply_filters('fluentform/transaction_view_url', $urlBase);
352
353 if (!strpos($urlBase, '?')) {
354 $urlBase .= '?';
355 } else {
356 $urlBase .= '&';
357 }
358
359 $wpDateTimeFormat = get_option('date_format') . ' ' . get_option('time_format');
360
361 $config = [
362 'new_tab' => false,
363 'view_text' => __('View', 'fluentform'),
364 'base_url' => $urlBase,
365 'date_format' => get_option('date_format'),
366 'date_time_format' => $wpDateTimeFormat,
367 'transactions_title' => __('Payments', 'fluentform'),
368 'subscriptions_title' => __('Subscriptions', 'fluentform'),
369 'sub_cancel_confirm_heading' => __('Are you sure you want to cancel this subscription?', 'fluentform'),
370 'sub_cancel_confirm_btn' => __('Yes, cancel this subscription', 'fluentform'),
371 'sub_cancel_close' => __('Close', 'fluentform')
372 ];
373
374 $config = apply_filters_deprecated(
375 'fluentform_payment_view_config',
376 [
377 $config
378 ],
379 FLUENTFORM_FRAMEWORK_UPGRADE,
380 'fluentform/payment_view_config',
381 'Use fluentform/payment_view_config instead of fluentform_payment_view_config.'
382 );
383
384 return apply_filters('fluentform/payment_view_config', $config);
385
386 }
387
388 private function verifyNonce()
389 {
390 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Nonce verification happens immediately below
391 $nonce = isset($_REQUEST['_nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['_nonce'])) : '';
392 if (!wp_verify_nonce($nonce, 'fluentform_transactions')) {
393 wp_send_json_error([
394 'message' => __('Security validation failed. Please try again', 'fluentform')
395 ], 423);
396 }
397 }
398
399 private function canCancelSubscription($subscription)
400 {
401 $validStatuses = [
402 'active',
403 'trialling',
404 'failing'
405 ];
406
407 if (!in_array($subscription->status, $validStatuses)) {
408 return false;
409 }
410
411 $paymentSettings = PaymentHelper::getPaymentSettings();
412 if ($paymentSettings['user_can_manage_subscription'] != 'yes') {
413 return false;
414 }
415
416 $submission = fluentFormApi('submissions')->find($subscription->submission_id);
417
418 if (!$submission) {
419 return false;
420 }
421
422 $method = $submission->payment_method;
423
424 $hasCancel = apply_filters_deprecated(
425 'fluentform_pay_method_has_sub_cancel_' . $method,
426 [
427 false
428 ],
429 FLUENTFORM_FRAMEWORK_UPGRADE,
430 'fluentform/pay_method_has_sub_cancel_' . $method,
431 'Use fluentform/pay_method_has_sub_cancel_' . $method . ' instead of fluentform_pay_method_has_sub_cancel_' . $method
432 );
433
434 return $method == 'stripe' || apply_filters('fluentform/pay_method_has_sub_cancel_' . $method, $hasCancel);
435 }
436
437 public function cancelSubscriptionAjax()
438 {
439 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified by verifyNonce() in routeAjaxEndpoints()
440 $subscriptionId = ArrayHelper::get($_REQUEST, 'subscription_id');
441
442 if (!$subscriptionId) {
443 $this->sendError(__('Invalid Request', 'fluentform'));
444 }
445
446 $subscription = fluentFormApi('submissions')->getSubscription($subscriptionId);
447
448 if (!$subscription) {
449 $this->sendError(__('No subscription found', 'fluentform'));
450 }
451
452 // validate the subscription
453 $userid = get_current_user_id();
454 $submission = fluentFormApi('submissions')->find($subscription->submission_id);
455
456 if (!$submission || !$this->canCancelSubscription($subscription)) {
457 $this->sendError(__('Sorry, you can not cancel this subscription at this moment', 'fluentform'));
458 }
459
460 $canManagePayments = Acl::hasPermission('fluentform_manage_payments', $submission->form_id);
461
462 if (!$canManagePayments && (int) $submission->user_id !== (int) $userid) {
463 $this->sendError(__('Sorry, you can not cancel this subscription at this moment', 'fluentform'));
464 }
465
466 $handler = apply_filters_deprecated(
467 'fluentform_payment_manager_class_' . $submission->payment_method,
468 [
469 false
470 ],
471 FLUENTFORM_FRAMEWORK_UPGRADE,
472 'fluentform/payment_manager_class_' . $submission->payment_method,
473 'Use fluentform/payment_manager_class_' . $submission->payment_method . ' instead of fluentform_payment_manager_class_' . $submission->payment_method
474 );
475
476 // Now let's try to cancel this subscription
477 $handler = apply_filters('fluentform/payment_manager_class_' . $submission->payment_method, $handler);
478
479 if (!$handler || !method_exists($handler, 'cancelSubscription')) {
480 $this->sendError(__('Sorry, you can not cancel this subscription at this moment', 'fluentform'));
481 }
482
483 $response = $handler->cancelSubscription($subscription, 'user', $submission);
484
485 if (is_wp_error($response)) {
486 $this->sendError($response->get_error_code() . ' - ' . $response->get_error_message());
487 }
488
489 wp_send_json_success([
490 'message' => __('Your subscription has been cancelled. Refreshing the page...', 'fluentform')
491 ], 200);
492
493 }
494
495 private function sendError($message)
496 {
497 wp_send_json_error([
498 'message' => $message
499 ], 423);
500 }
501
502 public function getTransactionHtml($transaction, $withHeader = false)
503 {
504 $orderItems = [];
505 $discountItems = [];
506
507 $transactionTotal = $subTotal = $orderTotal = PaymentHelper::formatMoney($transaction->payment_total, $transaction->currency);
508
509 if ($transaction->transaction_type == 'subscription') {
510 $total = PaymentHelper::formatMoney($transaction->payment_total, $transaction->currency);
511 $orderItems[] = (object) [
512 'formatted_item_price' => $total,
513 'formatted_line_total' => $total,
514 'item_name' => $transaction->subscription->plan_name.' ('.$transaction->subscription->item_name.')',
515 'quantity' => 1
516 ];
517 } else {
518 $receiptClass = new PaymentReceipt($transaction->submission);
519 $orderItems = $receiptClass->getOrderItems();
520 $discountItems = $receiptClass->getDiscountItems();
521 $subTotal = OrderData::calculateOrderItemsTotal($orderItems, true, $transaction->currency);
522 $orderTotal = OrderData::calculateOrderItemsTotal($orderItems, true, $transaction->currency, $discountItems);
523 }
524
525 return PaymentHelper::loadView('transaction_details', [
526 'transaction' => $transaction,
527 'transactionTotal' => $transactionTotal,
528 'discountItems' => $discountItems,
529 'items' => $orderItems,
530 'subTotal' => $subTotal,
531 'orderTotal' => $orderTotal,
532 ]);
533 }
534 }
535