PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.4.0
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.4.0
2.4.1 2.4.0 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 All 68 releases
better-payment / includes / Admin / DB.php

DB.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.4.0, at includes/Admin/DB.php

829 lines 34.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\Admin;
4
5 use Better_Payment\Lite\Classes\Helper;
6 use Better_Payment\Lite\Classes\ProSubscriptionTransactionData;
7
8 /**
9 * Exit if accessed directly
10 */
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 /**
16 * This class responsible for database work
17 * using wordpress functionality
18 * get_option and update_option.
19 *
20 * @since 0.0.1
21 */
22 class DB {
23
24 /**
25 * Helper's counting rule (an unconfirmed pre-2.4.0 `no_payment_required` row is not counted; a
26 * confirmed one adds 0), aliased so the two deprecated queries below spell it `self::…` exactly
27 * as Pro's AnalyticsModel copies do (AnalyticsModelParityTest compares the bodies).
28 */
29 private const COUNTED_ROW_SQL = Helper::COUNTED_ROW_SQL;
30 private const COUNTABLE_AMOUNT_SQL = Helper::COUNTABLE_AMOUNT_SQL;
31
32 public static function get_table_name() {
33 global $wpdb;
34
35 $table = "{$wpdb->prefix}better_payment";
36 return $table;
37 }
38 /**
39 * Get all default settings value.
40 *
41 * @param string $name
42 * @return array
43 * @since 0.0.1
44 */
45 public static function default_settings() {
46 return apply_filters('better_payment_option_default_settings', array(
47 'better_payment_settings_general_general_paypal' => 'yes',
48 'better_payment_settings_general_general_stripe' => 'yes',
49 'better_payment_settings_general_general_paystack' => '',
50 'better_payment_settings_general_general_user_dashboard' => 'yes',
51 'better_payment_settings_general_general_fundraising_campaign' => 'yes',
52 'better_payment_settings_general_general_email' => 'yes',
53 'better_payment_settings_general_general_currency' => 'USD',
54
55 'better_payment_settings_general_email_to' => '',
56 'better_payment_settings_general_email_subject' => '',
57 'better_payment_settings_general_email_message_admin' => '',
58 'better_payment_settings_general_email_from_email' => '',
59 'better_payment_settings_general_email_from_name' => '',
60 'better_payment_settings_general_email_reply_to' => '',
61 'better_payment_settings_general_email_cc' => '',
62 'better_payment_settings_general_email_bcc' => '',
63 'better_payment_settings_general_email_send_as' => 'html',
64
65 'better_payment_settings_general_email_subject_customer' => '',
66 'better_payment_settings_general_email_message_customer' => '',
67 'better_payment_settings_general_email_from_email_customer' => '',
68 'better_payment_settings_general_email_from_name_customer' => '',
69 'better_payment_settings_general_email_reply_to_customer' => '',
70 'better_payment_settings_general_email_cc_customer' => '',
71 'better_payment_settings_general_email_bcc_customer' => '',
72 'better_payment_settings_general_email_send_as_customer' => 'html',
73
74 'better_payment_settings_payment_paypal_live_mode' => 'no',
75 'better_payment_settings_payment_paypal_email' => '',
76 'better_payment_settings_opt_in' => '',
77 'better_payment_settings_payment_paypal_live_client_id' => '',
78 'better_payment_settings_payment_paypal_test_client_id' => '',
79 'better_payment_settings_payment_paypal_live_secret' => '',
80 'better_payment_settings_payment_paypal_test_secret' => '',
81 'better_payment_settings_payment_stripe_live_mode' => 'no',
82 'better_payment_settings_payment_stripe_live_public' => '',
83 'better_payment_settings_payment_stripe_live_secret' => '',
84 'better_payment_settings_payment_stripe_test_public' => '',
85 'better_payment_settings_payment_stripe_test_secret' => '',
86 'better_payment_settings_payment_paystack_live_mode' => 'no',
87 'better_payment_settings_payment_paystack_live_public' => '',
88 'better_payment_settings_payment_paystack_live_secret' => '',
89 'better_payment_settings_payment_paystack_test_public' => '',
90 'better_payment_settings_payment_paystack_test_secret' => '',
91
92 // AI Assistant (Campaign Builder). See includes/AI/.
93 'better_payment_settings_ai_enabled' => '',
94 'better_payment_settings_ai_provider' => 'openai',
95 'better_payment_settings_ai_model' => '',
96 'better_payment_settings_ai_image_model' => '',
97 'better_payment_settings_ai_temperature' => '0.7',
98 'better_payment_settings_ai_max_tokens' => '4096',
99 'better_payment_settings_ai_system_prompt' => '',
100 'better_payment_settings_ai_api_key_openai' => '',
101 'better_payment_settings_ai_api_key_claude' => '',
102 'better_payment_settings_ai_api_key_gemini' => '',
103 'better_payment_settings_ai_api_key_openrouter' => '',
104
105 // E-commerce > Subscription. Booleans follow the 'yes'/'no' string
106 // convention used across this settings array.
107 'better_payment_settings_ecommerce_subscription_renewal_process' => 'auto',
108 'better_payment_settings_ecommerce_subscription_stripe_auto_renew' => 'yes',
109 'better_payment_settings_ecommerce_subscription_auto_renewal_toggle' => 'yes',
110 'better_payment_settings_ecommerce_subscription_myaccount_tab' => 'yes',
111 'better_payment_settings_ecommerce_subscription_active_role' => 'subscriber',
112 'better_payment_settings_ecommerce_subscription_inactive_role' => 'customer',
113 ));
114 }
115 /**
116 * Get all settings value from options table.
117 *
118 * Returns the full settings array when $name is empty, otherwise the single
119 * value for that key (which may be a string, array, etc.).
120 *
121 * @param string $name
122 * @return mixed
123 * @since 0.0.1
124 */
125 public static function get_settings($name = '')
126 {
127 $settings = get_option('better_payment_settings', true);
128 $default = self::default_settings();
129
130 $settings = wp_parse_args($settings, $default);
131
132 if (!empty($name) && isset($settings[$name])) {
133 return $settings[$name];
134 }
135
136 if (!empty($name) && !isset($settings[$name]) && isset($default[$name])) {
137 return $default[$name];
138 }
139
140 if (!empty($name) && !isset($settings[$name]) && !isset($default[$name])) {
141 return '';
142 }
143
144 return is_array($settings) ? $settings : [];
145 }
146 /**
147 * Update settings
148 * @param array $value
149 * @return boolean
150 * @since 0.0.1
151 */
152 public static function update_settings($value, $key = '')
153 {
154 if (!empty($key)) {
155 return update_option($key, $value);
156 }
157 return update_option('better_payment_settings', $value);
158 }
159
160 /**
161 * Get all transactions from better payment table.
162 *
163 * @param array|string $args Filters, as wp_parse_args() reads them (every caller passes an array).
164 * @return array
165 * @since 0.0.1
166 */
167 public static function get_transactions($args = [], $count_only=0, $version = 'v1', $fetchNull = 0, $transaction_type = '')
168 {
169 global $wpdb;
170 $items = '';
171
172 $defaults = array(
173 'search_text' => '',
174 'payment_date_from' => '',
175 'payment_date_to' => '',
176 'order_by' => 'payment_date',
177 'order' => 'DESC',
178 'currency' => 'all',
179 'source' => '',
180 'paged' => 1,
181 'per_page' => 20,
182 'offset' => 0,
183 'status' => 'all',
184 );
185
186 $allowed_order_by = array(
187 'id',
188 'payment_date',
189 'email',
190 'amount'
191 );
192
193 $allowed_order = array(
194 'DESC',
195 'ASC'
196 );
197
198 $better_payment_db_obj = new DB();
199 $better_payment_helper_obj = new Helper();
200
201 $allowed_statuses = $better_payment_db_obj->allowed_statuses('all', 'v2');
202 $allowed_statuses_types = $better_payment_db_obj->allowed_statuses_types('v2');
203
204 $args = wp_parse_args($args, $defaults);
205 $table = self::get_table_name();
206
207 $search_text = sanitize_text_field($args['search_text']);
208 $search_text = empty($search_text) ? '' : "%$search_text%";
209
210 $order_by = in_array($args['order_by'], $allowed_order_by) ? sanitize_text_field($args['order_by']) : $defaults['order_by'];
211 $order = in_array($args['order'], $allowed_order) ? sanitize_text_field($args['order']) : $defaults['order'];
212 $offset = $args['offset'] > 0 ? intval($args['offset']) : $defaults['offset'];
213 $per_page = $args['per_page'] > 0 ? intval($args['per_page']) : $defaults['per_page'];
214
215 if($args['status'] == 'null') {
216 $status = sanitize_text_field($args['status']);
217 }else {
218 $allowed_statuses_by_version = ($version == 'v1') ? $allowed_statuses : $allowed_statuses_types;
219 $status = in_array($args['status'], $allowed_statuses_by_version) ? sanitize_text_field($args['status']) : $defaults['status'];
220 }
221
222 $statuses_by_transaction_type = array();
223
224 if( !empty($transaction_type) ){
225 $statuses_by_types = $better_payment_helper_obj->transaction_statuses_with_type_v2();
226 $transaction_type = is_string( $transaction_type ) ? $transaction_type : 'all';
227
228 if ( 'null' === $transaction_type ) {
229 // The count twin of `status=null` below.
230 $status = 'null';
231 $transaction_type = '';
232 } else {
233 // An unknown type is every row, as an unknown `status` is. It used to fall back to the
234 // string 'all' and fatal in array_map() below.
235 $transaction_type = isset( $statuses_by_types[ $transaction_type ] ) ? $transaction_type : 'all';
236 $statuses_by_transaction_type = $statuses_by_types[ $transaction_type ]['statuses'];
237 $status = '';
238 }
239 }
240
241 $whereQuery = '';
242
243 $valid_search_text = $search_text != '' && strlen($search_text) >= 2;
244 if($valid_search_text){
245 $whereQuery = $wpdb->prepare(' AND ( email LIKE %s OR form_fields_info LIKE %s OR transaction_id LIKE %s OR amount LIKE %s OR source LIKE %s ) ', $search_text, $search_text ,$search_text, $search_text, $search_text );
246 }
247
248 if( $args['source'] != '' ){
249 $whereQuery .= $wpdb->prepare(" AND source = %s", $args['source']);
250 }
251
252 if( $args['currency'] != 'all' ){
253 $whereQuery .= $wpdb->prepare(" AND currency = %s", $args['currency']);
254 }
255
256 if( $status != 'all' ){
257 if($status == 'null'){
258 $whereQuery .= esc_sql(" AND status is NULL");
259 }else {
260 //version 2
261 if('v1' === $version){
262 $whereQuery .= $wpdb->prepare( " AND status = %s OR status = %s", $status, strtolower($status) );
263 } else {
264
265 if($transaction_type){
266 $statuses_by_type = $statuses_by_transaction_type;
267 }else {
268 $statuses_by_type = $better_payment_db_obj->allowed_statuses($status, 'v2');
269 }
270
271 $statuses = array_map(function($status) {
272 return "'" . esc_sql($status) . "'";
273 }, $statuses_by_type);
274
275 $statuses = implode(',', $statuses);
276
277 $bucket = $transaction_type ? $transaction_type : $status;
278
279 // NULL is the incomplete bucket's own status (PayPal's pending sentinel): `IN ()`
280 // cannot match it, so the Incomplete tab always ORs it in and lists the rows its
281 // total and stat card count.
282 $nullDataQuery = ( $fetchNull || 'incomplete' === $bucket ) ? " OR status IS NULL " : '';
283
284 // The counting rule, for the tabs: an unconfirmed pre-2.4.0 `no_payment_required`
285 // row is in no count, so it is listed under Incomplete, never Completed, as
286 // Helper::transaction_status_type() labels it. A confirmed one stays Completed;
287 // `refunded` is unchanged. Helper constants only (no request data, no `%`).
288 $unconfirmed_sql = 'incomplete' === $bucket ? ' OR ' . Helper::UNCONFIRMED_LEGACY_SQL . ' ' : '';
289 $counted_sql = 'completed' === $bucket ? ' AND ' . Helper::COUNTED_ROW_SQL . ' ' : '';
290
291 // `all` is every row, as the All tab's list is: its `IN ()` list would leave NULL and
292 // unlisted statuses out of the total.
293 if ( 'all' !== $bucket ) {
294 $whereQuery .= " AND ( status IN ( $statuses ) " . esc_sql($nullDataQuery) . $unconfirmed_sql . " ) " . $counted_sql;
295 }
296 }
297 }
298 }
299
300 if( $args['payment_date_from'] != '' && $args['payment_date_to'] != ''){
301 $whereQuery .= $wpdb->prepare(" AND payment_date BETWEEN %s AND %s", $args['payment_date_from'], $args['payment_date_to']);
302 }
303
304 if($count_only === 1){
305 if($valid_search_text){
306 $items = $wpdb->get_var(
307 "SELECT count(id) FROM $table WHERE 1
308 $whereQuery
309 ORDER BY $order_by $order
310 "
311 );
312 }else {
313 $items = $wpdb->get_var(
314 $wpdb->prepare(
315 "SELECT count(id) FROM $table WHERE 1
316 $whereQuery
317 ORDER BY $order_by $order
318 LIMIT %d, %d
319 ",
320 $offset,
321 $per_page
322 )
323 );
324 }
325
326 }else {
327 $items = $wpdb->get_results(
328 $wpdb->prepare(
329 "SELECT * FROM $table WHERE 1
330 $whereQuery
331 ORDER BY $order_by $order
332 LIMIT %d, %d
333 ",
334 $offset,
335 $per_page
336 )
337 );
338
339 }
340
341 return $items;
342 }
343
344 /**
345 * Get the count of total transactions
346 *
347 * @return int
348 * @since 0.0.1
349 */
350 public static function get_transaction_count($args = '', $version = 'v1', $fetchNull = 0, $transaction_type = '')
351 {
352 global $wpdb;
353 $table = self::get_table_name();
354
355 $count = 0;
356
357 if( !empty($transaction_type) ){
358 $count = self::get_transactions($args, 1, $version, $fetchNull, $transaction_type);
359 return $count;
360 }
361
362 if($args !== ''){
363 $count = self::get_transactions($args, 1, $version, $fetchNull);
364 }else {
365 $count = $wpdb->get_var(
366 "SELECT count(id) FROM $table"
367 );
368 }
369
370 return $count;
371 }
372
373 /**
374 * Get transactions analytics
375 *
376 * @return array<string, mixed>
377 * @since 0.0.1
378 */
379 public static function get_transactions_analytics_dashboard($filtered_transactions = array())
380 {
381 global $wpdb;
382 $transaction_analytics = [];
383
384 $table = self::get_table_name();
385 $better_payment_db_obj = new DB();
386
387 $total_transactions_count = 0;
388 $completed_transactions_count = 0;
389 $incomplete_transactions_count = 0;
390 $refunded_transactions_count = 0;
391
392 $total_transactions_amount = 0;
393 $completed_transactions_amount = 0;
394 $incomplete_transactions_amount = 0;
395 $refunded_transactions_amount = 0;
396
397 if ( is_array( $filtered_transactions ) && count( $filtered_transactions ) ) {
398 $total_transactions_count = $better_payment_db_obj->get_transactions_amount_by_statuses(array(), '', '', 'count', 1, $filtered_transactions );
399 $total_transactions_amount = $better_payment_db_obj->get_transactions_amount_by_statuses(array(), '', '', 'amount', 1, $filtered_transactions );
400
401 $completed_statuses = $better_payment_db_obj->allowed_statuses('completed', 'v2');
402 $completed_transactions_count = $better_payment_db_obj->get_transactions_amount_by_statuses( $completed_statuses, '', '', 'count', 0, $filtered_transactions );
403 $completed_transactions_amount = $better_payment_db_obj->get_transactions_amount_by_statuses( $completed_statuses, '', '', 'amount', 0, $filtered_transactions );
404
405 $incomplete_statuses = $better_payment_db_obj->allowed_statuses('incomplete', 'v2');
406 $incomplete_transactions_count = $better_payment_db_obj->get_transactions_amount_by_statuses( $incomplete_statuses, '', '', 'count', 1, $filtered_transactions );
407 $incomplete_transactions_amount = $better_payment_db_obj->get_transactions_amount_by_statuses( $incomplete_statuses, '', '', 'amount', 1, $filtered_transactions );
408
409 $refunded_statuses = $better_payment_db_obj->allowed_statuses('refunded', 'v2');
410 $refunded_transactions_count = $better_payment_db_obj->get_transactions_amount_by_statuses( $refunded_statuses, '', '', 'count', 0, $filtered_transactions );
411 $refunded_transactions_amount = $better_payment_db_obj->get_transactions_amount_by_statuses( $refunded_statuses, '', '', 'amount', 0, $filtered_transactions );
412 }
413
414 $transaction_analytics['total_transactions_count'] = $total_transactions_count;
415 $transaction_analytics['completed_transactions_count'] = $completed_transactions_count;
416 $transaction_analytics['incomplete_transactions_count'] = $incomplete_transactions_count;
417 $transaction_analytics['refunded_transactions_count'] = $refunded_transactions_count;
418
419 $transaction_analytics['total_transactions_amount'] = $total_transactions_amount;
420 $transaction_analytics['completed_transactions_amount'] = $completed_transactions_amount;
421 $transaction_analytics['incomplete_transactions_amount'] = $incomplete_transactions_amount;
422 $transaction_analytics['refunded_transactions_amount'] = $refunded_transactions_amount;
423
424 // Recent transactions
425
426 return $transaction_analytics;
427 }
428
429 /**
430 * Get transaction details from better payment table.
431 *
432 * @param string $args
433 * @return object
434 * @since 0.0.1
435 */
436 public static function get_transaction($id)
437 {
438 global $wpdb;
439
440 $table = self::get_table_name();
441 $transaction_id = (int) sanitize_text_field($id);
442
443 $item = $wpdb->get_row(
444 $wpdb->prepare(
445 "SELECT * FROM $table WHERE id = %d",
446 $transaction_id
447 )
448 );
449
450 return $item;
451 }
452
453 /**
454 * Get analytics counts and amounts for a specific user via a single SQL aggregate query.
455 * Returns the same shape as get_transactions_analytics_dashboard() without loading all rows.
456 *
457 * An unconfirmed pre-2.4.0 `no_payment_required` row is left out of every figure; a confirmed
458 * one counts as completed and adds 0 to the amounts (Helper::COUNTED_ROW_SQL /
459 * Helper::COUNTABLE_AMOUNT_SQL).
460 *
461 * @param string $email
462 * @return array
463 */
464 public static function get_user_analytics_by_email( $email ) {
465 global $wpdb;
466 $table = self::get_table_name();
467 $email = sanitize_text_field( $email );
468 $ffi_like = '%' . $wpdb->esc_like( '"' . $email . '"' ) . '%';
469
470 // Helper constants only: no request data, and no `%` for prepare() to misread.
471 $countable_amount = 'CAST(' . Helper::COUNTABLE_AMOUNT_SQL . ' AS DECIMAL(10,2))';
472 $completed = "status IN ('paid','Completed','completed','success','" . Helper::LEGACY_FREE_CHECKOUT_STATUS . "')";
473 $counted_row = Helper::COUNTED_ROW_SQL;
474
475 $row = $wpdb->get_row(
476 $wpdb->prepare(
477 "SELECT
478 COUNT(*) AS total_count,
479 SUM({$countable_amount}) AS total_amount,
480 SUM(CASE WHEN {$completed}
481 THEN 1 ELSE 0 END) AS completed_count,
482 SUM(CASE WHEN {$completed}
483 THEN {$countable_amount} ELSE 0 END) AS completed_amount,
484 SUM(CASE WHEN status IN ('pending','Pending','unpaid','incomplete','Incomplete','failed') OR status IS NULL
485 THEN 1 ELSE 0 END) AS incomplete_count,
486 SUM(CASE WHEN status IN ('pending','Pending','unpaid','incomplete','Incomplete','failed') OR status IS NULL
487 THEN CAST(amount AS DECIMAL(10,2)) ELSE 0 END) AS incomplete_amount,
488 SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) AS refunded_count,
489 SUM(CASE WHEN status = 'refunded'
490 THEN CAST(amount AS DECIMAL(10,2)) ELSE 0 END) AS refunded_amount
491 FROM $table WHERE ( email = %s OR form_fields_info LIKE %s ) AND {$counted_row}",
492 $email,
493 $ffi_like
494 )
495 );
496
497 if ( ! $row ) {
498 $row = (object) array_fill_keys(
499 array( 'total_count', 'total_amount', 'completed_count', 'completed_amount', 'incomplete_count', 'incomplete_amount', 'refunded_count', 'refunded_amount' ),
500 0
501 );
502 }
503
504 return [
505 'total_transactions_count' => (int) $row->total_count,
506 'total_transactions_amount' => (float) $row->total_amount,
507 'completed_transactions_count' => (int) $row->completed_count,
508 'completed_transactions_amount' => (float) $row->completed_amount,
509 'incomplete_transactions_count' => (int) $row->incomplete_count,
510 'incomplete_transactions_amount' => (float) $row->incomplete_amount,
511 'refunded_transactions_count' => (int) $row->refunded_count,
512 'refunded_transactions_amount' => (float) $row->refunded_amount,
513 ];
514 }
515
516 /**
517 * Get paginated transactions for a specific user email.
518 *
519 * @param string $email
520 * @param array $args page, per_page, type ('transactions'|'subscriptions')
521 * @return array { transactions: array, total: int, pages: int }
522 */
523 public static function get_user_transactions_paginated( $email, $args = [] ) {
524 global $wpdb;
525
526 $defaults = [
527 'page' => 1,
528 'per_page' => 10,
529 'type' => 'transactions',
530 ];
531 $args = wp_parse_args( $args, $defaults );
532 $table = self::get_table_name();
533 $email = sanitize_text_field( $email );
534 $page = max( 1, intval( $args['page'] ) );
535 $per_page = max( 1, intval( $args['per_page'] ) );
536 $offset = ( $page - 1 ) * $per_page;
537
538 $type_where = self::user_transactions_type_where( (string) $args['type'] );
539 if ( null === $type_where ) {
540 // Nothing defines this row type (e.g. 'subscriptions' without Pro): answer
541 // with no rows, never with every row the user owns.
542 return [
543 'transactions' => [],
544 'total' => 0,
545 'pages' => 1,
546 'page' => $page,
547 'per_page' => $per_page,
548 ];
549 }
550
551 // Match rows where the email column is set directly OR where the email
552 // appears as a quoted value inside the serialized form_fields_info blob.
553 // The initial payment_create() insert never writes the email column —
554 // only the payment-confirmed callback does — so many rows have the user's
555 // email exclusively inside form_fields_info.
556 $ffi_like = '%' . $wpdb->esc_like( '"' . $email . '"' ) . '%';
557
558 $email_where = 'email = %s OR form_fields_info LIKE %s';
559
560 $total = (int) $wpdb->get_var(
561 $wpdb->prepare(
562 "SELECT COUNT(id) FROM $table WHERE ( $email_where )" . $type_where,
563 $email,
564 $ffi_like
565 )
566 );
567
568 $transactions = $wpdb->get_results(
569 $wpdb->prepare(
570 "SELECT * FROM $table WHERE ( $email_where )" . $type_where . " ORDER BY payment_date DESC LIMIT %d OFFSET %d",
571 $email,
572 $ffi_like,
573 $per_page,
574 $offset
575 )
576 );
577
578 return [
579 'transactions' => $transactions,
580 'total' => $total,
581 'pages' => $total > 0 ? (int) ceil( $total / $per_page ) : 1,
582 'page' => $page,
583 'per_page' => $per_page,
584 ];
585 }
586
587 /**
588 * The SQL condition narrowing get_user_transactions_paginated() to a row type.
589 *
590 * 'transactions' (or no type) is every row the user owns. Every other type is
591 * defined by `better_payment/db/user_transactions_type_where` ($where, $type),
592 * which starts from null: Pro supplies the 'subscriptions' condition for its
593 * recurring and split payments. Lite's copy of it
594 * (ProSubscriptionTransactionData::subscriptions_type_where()) serves only an
595 * active Pro that does not (<= 1.4.3). A type nobody defines returns null, and
596 * the caller answers with no rows.
597 *
598 * @since 2.4.0
599 *
600 * @param string $type Requested row type.
601 * @return string|null SQL fragment starting with " AND", '' for all rows, null for none.
602 */
603 private static function user_transactions_type_where( string $type ) {
604 if ( '' === $type || 'transactions' === $type ) {
605 return '';
606 }
607
608 $where = null;
609
610 if ( ProSubscriptionTransactionData::lite_copy_applies() ) {
611 $where = ProSubscriptionTransactionData::subscriptions_type_where( $where, $type );
612 }
613
614 $where = apply_filters( 'better_payment/db/user_transactions_type_where', $where, $type );
615
616 return is_string( $where ) ? $where : null;
617 }
618
619 /**
620 * Delete a transaction
621 *
622 * @param int $id
623 *
624 * @return int|boolean
625 * @since 0.0.1
626 */
627 public static function delete_transaction($id)
628 {
629 global $wpdb;
630 $table = self::get_table_name();
631
632 return $wpdb->delete(
633 $table,
634 ['id' => $id],
635 ['%d']
636 );
637 }
638
639 /**
640 * Allowed transaction statuses
641 *
642 * @since 0.0.1
643 */
644 public function allowed_statuses($type = 'all', $version = 'v1'){
645 $helperObj = new Helper();
646 $statuses = $helperObj->get_statuses_by_transaction_type($type, $version);
647
648 return $statuses;
649 }
650
651 /**
652 * Allowed transaction statuses
653 *
654 * @since 0.0.1
655 */
656 public function allowed_statuses_types($version = 'v1'){
657 $helperObj = new Helper();
658 $statuses = $helperObj->get_transaction_types($version);
659
660 return $statuses;
661 }
662
663 /**
664 * Get transactions amount
665 *
666 * Lite never calls this; Better Payment Pro <= 1.4.3's AnalyticsModel does, and
667 * tests/Unit/ProCouplingContractTest.php pins it. Pro 1.4.4 carries its own copy.
668 *
669 * @deprecated 2.4.0 Kept only for Better Payment Pro <= 1.4.3; delete when MIN_PRO_VERSION >= 1.4.4.
670 * Pro copy: better-payment-pro/includes/Models/AnalyticsModel.php::get_transactions_by_statuses().
671 *
672 * @return array
673 * @since 0.0.1
674 */
675 public function get_transactions_by_statuses($statuses = array(), $payment_date_from = '', $payment_date_to = '', $fetchNull=0) {
676 global $wpdb;
677
678 $table = DB::get_table_name();
679 $allowed_statuses = $this->allowed_statuses('all', 'v2');
680
681 $nullDataQuery = $fetchNull ? " OR status IS NULL " : '';
682
683 $statuses = is_array($statuses) && count($statuses) ? $statuses : $allowed_statuses;
684
685 #ToDo: Consider currency while calculating total amount
686 $statuses = array_map(function($status) use ($allowed_statuses) {
687 if(!in_array($status, $allowed_statuses)) {
688 return false;
689 }
690
691 return "'" . esc_sql($status) . "'";
692 }, $statuses);
693
694 $statuses = implode(',', $statuses);
695
696 if(!empty($payment_date_from) && !empty($payment_date_to)){
697 $amount = $wpdb->get_results(
698 "SELECT " . self::COUNTABLE_AMOUNT_SQL . " AS amount, payment_date, email FROM $table WHERE ( status IN (" . $statuses . ") " . esc_sql($nullDataQuery) . ") AND " . self::COUNTED_ROW_SQL . " AND payment_date BETWEEN '" . esc_sql($payment_date_from) . "' AND '" . esc_sql($payment_date_to) . "'"
699 );
700 } else if(!empty($payment_date_from) && empty($payment_date_to)){
701 $amount = $wpdb->get_results(
702 "SELECT " . self::COUNTABLE_AMOUNT_SQL . " AS amount, payment_date, email FROM $table WHERE ( status IN (" . $statuses . ") " . esc_sql($nullDataQuery) . ") AND " . self::COUNTED_ROW_SQL . " AND payment_date >= '" . esc_sql($payment_date_from) . "'"
703 );
704 } else {
705 $amount = $wpdb->get_results(
706 "SELECT " . self::COUNTABLE_AMOUNT_SQL . " AS amount, payment_date, email FROM $table WHERE ( status IN (" . $statuses . ") " . esc_sql($nullDataQuery) . " ) AND " . self::COUNTED_ROW_SQL
707 );
708 }
709
710 return $amount;
711 }
712
713 /**
714 * Get transactions amount
715 *
716 * Lite's caller, get_transactions_analytics_dashboard(), always passes
717 * $filtered_transactions and is answered from that array. Without it the answer
718 * comes from SQL, which only Better Payment Pro <= 1.4.3's AnalyticsModel asks
719 * for — see transactions_amount_by_statuses_query().
720 *
721 * @return mixed
722 * @since 0.0.1
723 */
724 public function get_transactions_amount_by_statuses($statuses = array(), $payment_date_from = '', $payment_date_to = '', $count_or_amount = 'amount', $fetchNull=0, $filtered_transactions = array() ) {
725 $allowed_statuses = $this->allowed_statuses('all', 'v2');
726
727 $nullDataQuery = $fetchNull ? " OR status IS NULL " : '';
728
729 $statuses = is_array($statuses) && count($statuses) ? $statuses : $allowed_statuses;
730 $statuses_original = $statuses;
731
732 #ToDo: Consider currency while calculating total amount
733 $statuses = array_map(function($status) use ($allowed_statuses) {
734 if(!in_array($status, $allowed_statuses)) {
735 return false;
736 }
737
738 return "'" . esc_sql($status) . "'";
739 }, $statuses);
740
741 $statuses = implode(',', $statuses);
742
743 if( is_array($filtered_transactions) && count($filtered_transactions) ){
744 $filtered_transactions_by_statuses = array();
745 $filtered_transactions_amount = 0;
746
747 foreach($filtered_transactions as $filtered_transaction) {
748 // Helper's counting rule, as the SQL copies below apply it: an unconfirmed pre-2.4.0
749 // free checkout is no transaction to count, and a confirmed one adds 0.
750 if ( Helper::is_unconfirmed_legacy_free_checkout( $filtered_transaction ) ) {
751 continue;
752 }
753
754 if( in_array($filtered_transaction->status, $statuses_original) ) {
755 $filtered_transactions_by_statuses[] = $filtered_transaction->id;
756 $filtered_transactions_amount += Helper::countable_amount( $filtered_transaction );
757 }
758 }
759
760 if($count_or_amount == 'count'){
761 $filtered_transactions_by_statuses_count = count($filtered_transactions_by_statuses);
762 return $filtered_transactions_by_statuses_count;
763 }
764
765 if($count_or_amount == 'amount'){
766 return $filtered_transactions_amount;
767 }
768 }
769
770 return $this->transactions_amount_by_statuses_query( $statuses, $nullDataQuery, $payment_date_from, $payment_date_to, $count_or_amount );
771 }
772
773 /**
774 * The SQL count or sum behind get_transactions_amount_by_statuses() when no
775 * filtered transactions are passed.
776 *
777 * @deprecated 2.4.0 Kept only for Better Payment Pro <= 1.4.3; delete when MIN_PRO_VERSION >= 1.4.4.
778 * Pro copy: better-payment-pro/includes/Models/AnalyticsModel.php::transactions_amount_by_statuses_query(),
779 * kept identical by Pro's AnalyticsModelParityTest.
780 *
781 * @param string $statuses Quoted, comma-separated statuses for IN().
782 * @param string $nullDataQuery " OR status IS NULL " or ''.
783 * @param string $payment_date_from Lower payment_date bound.
784 * @param string $payment_date_to Upper payment_date bound.
785 * @param string $count_or_amount 'count' or 'amount'.
786 * @return mixed
787 */
788 private function transactions_amount_by_statuses_query( $statuses, $nullDataQuery, $payment_date_from, $payment_date_to, $count_or_amount ) {
789 global $wpdb;
790
791 $table = DB::get_table_name();
792
793 if($count_or_amount == 'count') {
794 $count = (int) $wpdb->get_var(
795 "SELECT count(id) FROM $table WHERE ( status IN (" . $statuses . ") " . esc_sql($nullDataQuery) . " ) AND " . self::COUNTED_ROW_SQL
796 );
797
798 return $count;
799 }
800
801 if(!empty($payment_date_from) && !empty($payment_date_to)){
802 $amount = $wpdb->get_var(
803 "SELECT sum(" . self::COUNTABLE_AMOUNT_SQL . ") FROM $table WHERE ( status IN (" . $statuses . ") " . esc_sql($nullDataQuery) . ") AND " . self::COUNTED_ROW_SQL . " AND payment_date BETWEEN '" . esc_sql($payment_date_from) . "' AND '" . esc_sql($payment_date_to) . "'"
804 );
805 }else {
806 $amount = $wpdb->get_var(
807 // #TODO Amount based on filtered transactions
808 "SELECT sum(" . self::COUNTABLE_AMOUNT_SQL . ") FROM $table WHERE ( status IN (" . $statuses . ") " . esc_sql($nullDataQuery) . ") AND " . self::COUNTED_ROW_SQL
809 );
810 }
811
812 return $amount;
813 }
814
815 public static function mark_as_completed($id)
816 {
817 global $wpdb;
818 $table = self::get_table_name();
819
820 $id = (int) sanitize_text_field($id);
821
822 if ($id > 0) {
823 return $wpdb->update($table, ['status' => 'Completed'], ['id' => $id]);
824 }
825
826 return false;
827 }
828 }
829