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 / TransactionShortcodes.php

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

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