| @@ -55,9 +55,17 @@ | ||
| 55 | 55 | { |
| 56 | 56 | register_rest_route($this->namespace, '/transactions', [ |
| 57 | 57 | 'methods' => WP_REST_Server::READABLE, |
| 58 | 58 | 'callback' => [$this, 'get_transactions'], |
| 59 | - 'permission_callback' => [$this, 'check_admin_permissions'] | |
| 59 | + 'permission_callback' => [$this, 'check_admin_permissions'], | |
| 60 | + 'args' => [ | |
| 61 | + // A tab. Anything else is `all` (no `type`, so a non-string falls back too instead of a 400). | |
| 62 | + 'status' => [ | |
| 63 | + 'required' => false, | |
| 64 | + 'default' => 'all', | |
| 65 | + 'sanitize_callback' => [__CLASS__, 'sanitize_transaction_status'] | |
| 66 | + ] | |
| 67 | + ] | |
| 60 | 68 | ]); |
| 61 | 69 | |
| 62 | 70 | register_rest_route($this->namespace, '/subscriptions', [ |
| 63 | 71 | 'methods' => WP_REST_Server::READABLE, |
| @@ -388,8 +396,28 @@ | ||
| 388 | 396 | return true; |
| 389 | 397 | } |
| 390 | 398 | |
| 391 | 399 | /** |
| 400 | + * The `status` param of GET /transactions: a v2 tab (`all`, `completed`, `incomplete`, `refunded`). | |
| 401 | + * | |
| 402 | + * Anything else is `all`, as DB::get_transactions() reads an unknown status. An unknown value used | |
| 403 | + * to reach the count query as a transaction type and fatal in array_map() over a string. Runs as | |
| 404 | + * the route arg's sanitize_callback and again in the handler. | |
| 405 | + * | |
| 406 | + * @since 2.4.0 | |
| 407 | + * | |
| 408 | + * @param mixed $value Raw param. | |
| 409 | + * @return string | |
| 410 | + */ | |
| 411 | + public static function sanitize_transaction_status($value): string | |
| 412 | + { | |
| 413 | + $status = is_string($value) ? sanitize_text_field($value) : ''; | |
| 414 | + $tabs = (new \Better_Payment\Lite\Classes\Helper())->get_transaction_types('v2'); | |
| 415 | + | |
| 416 | + return in_array($status, $tabs, true) ? $status : 'all'; | |
| 417 | + } | |
| 418 | + | |
| 419 | + /** | |
| 392 | 420 | * Get transactions with filters |
| 393 | 421 | * |
| 394 | 422 | * @since 1.5.0 |
| 395 | 423 | * @param WP_REST_Request $request |
| @@ -403,9 +431,9 @@ | ||
| 403 | 431 | |
| 404 | 432 | try { |
| 405 | 433 | $paged = $request->get_param('paged') ? intval($request->get_param('paged')) : 1; |
| 406 | 434 | $per_page = $request->get_param('per_page') ? intval($request->get_param('per_page')) : 20; |
| 407 | - $status = $request->get_param('status') ? sanitize_text_field($request->get_param('status')) : 'all'; | |
| 435 | + $status = self::sanitize_transaction_status($request->get_param('status')); | |
| 408 | 436 | $search_text = $request->get_param('search_text') ? sanitize_text_field($request->get_param('search_text')) : ''; |
| 409 | 437 | $payment_date_from = $request->get_param('payment_date_from') ? sanitize_text_field($request->get_param('payment_date_from')) : ''; |
| 410 | 438 | $payment_date_to = $request->get_param('payment_date_to') ? sanitize_text_field($request->get_param('payment_date_to')) : ''; |
| 411 | 439 | $order_by = $request->get_param('order_by') ? sanitize_text_field($request->get_param('order_by')) : 'payment_date'; |
| @@ -444,8 +472,11 @@ | ||
| 444 | 472 | |
| 445 | 473 | $transactions = DB::get_transactions($filters, 0, 'v2'); |
| 446 | 474 | |
| 447 | 475 | foreach ($transactions as $key => $transaction) { |
| 476 | + // The bucket the row is labelled under (an unconfirmed pre-2.4.0 free checkout is | |
| 477 | + // `incomplete`). The React list prefers it to its own raw-status map. | |
| 478 | + $transactions[$key]->status_type = \Better_Payment\Lite\Classes\Helper::transaction_status_type($transaction, 'v2'); | |
| 448 | 479 | $transactions[$key]->form_fields_info = maybe_unserialize($transaction->form_fields_info); |
| 449 | 480 | } |
| 450 | 481 | unset($filters['offset']); |
| 451 | 482 | $total = DB::get_transaction_count($filters, 'v2', null, $status); |
| @@ -960,18 +991,20 @@ | ||
| 960 | 991 | |
| 961 | 992 | try { |
| 962 | 993 | $id = $request->get_param('id'); |
| 963 | 994 | $transaction = DB::get_transaction($id); |
| 995 | + | |
| 996 | + if (!$transaction) { | |
| 997 | + return new WP_Error('transaction_not_found', 'Transaction not found', ['status' => 404]); | |
| 998 | + } | |
| 999 | + | |
| 1000 | + // From the stored row, before form_fields_info is unserialized (see get_transactions()). | |
| 1001 | + $transaction->status_type = \Better_Payment\Lite\Classes\Helper::transaction_status_type($transaction, 'v2'); | |
| 964 | 1002 | $transaction->form_fields_info = maybe_unserialize($transaction->form_fields_info); |
| 965 | 1003 | $woo_products = maybe_unserialize($transaction->form_fields_info['detailed_product_info'] ?? []); |
| 966 | 1004 | $transaction->woo_products = $woo_products['woo_products'] ?? []; |
| 967 | 1005 | $transaction->woo_products = array_values($transaction->woo_products); |
| 968 | 1006 | |
| 969 | - | |
| 970 | - if (!$transaction) { | |
| 971 | - return new WP_Error('transaction_not_found', 'Transaction not found', ['status' => 404]); | |
| 972 | - } | |
| 973 | - | |
| 974 | 1007 | return rest_ensure_response($transaction); |
| 975 | 1008 | } catch (\Exception $e) { |
| 976 | 1009 | return new WP_Error('transaction_error', $e->getMessage(), ['status' => 500]); |
| 977 | 1010 | } |
| @@ -1000,8 +1033,15 @@ | ||
| 1000 | 1033 | // Check if transaction exists |
| 1001 | 1034 | $transaction = DB::get_transaction($id); |
| 1002 | 1035 | if (!$transaction) { |
| 1003 | 1036 | return new WP_Error('transaction_not_found', 'Transaction not found', ['status' => 404]); |
| 1037 | + } | |
| 1038 | + | |
| 1039 | + // A pre-2.4.0 free Stripe checkout stores the form's full amount although nothing was | |
| 1040 | + // charged, so `Completed` would count money that never arrived. Such rows are never | |
| 1041 | + // rewritten; Helper's counting rule decides them (docs/features/transactions.md). | |
| 1042 | + if (\Better_Payment\Lite\Classes\Helper::LEGACY_FREE_CHECKOUT_STATUS === $transaction->status) { | |
| 1043 | + return new WP_Error('legacy_free_checkout', __('This free checkout cannot be marked as completed: its stored amount was never charged.', 'better-payment'), ['status' => 400]); | |
| 1004 | 1044 | } |
| 1005 | 1045 | |
| 1006 | 1046 | // Check if transaction is already completed |
| 1007 | 1047 | $completed_statuses = ['Completed', 'completed', 'Paid', 'paid', 'Success', 'success', 'Refunded', 'refunded', 'Failed', 'failed']; |