init_hooks(); } /** * Initialize hooks. */ private function init_hooks() { add_action( 'template_redirect', array( $this, 'handle_receipt_request' ) ); } /** * Generate receipt number. * * @param int $order_id Order ID. * @return string */ public function generate_receipt_number( $order_id ) { $year = wp_date( 'Y' ); // Get counters from options. $counters = get_option( 'afridirect_receipt_counters', array() ); // Initialize counter for current year if not exists. if ( ! isset( $counters[ $year ] ) ) { $counters[ $year ] = 0; } // Increment counter atomically. $counters[ $year ]++; // Update option. update_option( 'afridirect_receipt_counters', $counters ); // Format: ADP-YYYY-000001 $receipt_num = sprintf( 'ADP-%s-%06d', $year, $counters[ $year ] ); return $receipt_num; } /** * Handle receipt page request. */ public function handle_receipt_request() { // This is a read-only, bookmarkable link (also delivered by email), so a nonce // is not appropriate. Input origin is validated instead by requiring both a // logged-in owner (or shop manager) AND the order's unguessable order key, // the same mechanism WooCommerce uses for "view order" links. // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( ! isset( $_GET['afridirect_receipt'] ) ) { return; } // Require an authenticated user. if ( ! is_user_logged_in() ) { wp_die( esc_html__( 'You must be logged in to view this receipt.', 'afridirect-payment' ) ); } $receipt_number = sanitize_text_field( wp_unslash( $_GET['afridirect_receipt'] ) ); $order_key = isset( $_GET['afridirect_order_key'] ) ? sanitize_text_field( wp_unslash( $_GET['afridirect_order_key'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( '' === $receipt_number ) { wp_die( esc_html__( 'Invalid receipt link.', 'afridirect-payment' ) ); } // Find order by receipt number (HPOS compatible). $orders = wc_get_orders( array( 'limit' => 1, // No non-meta way to look up an order by its generated receipt number. // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => '_afridirect_receipt_no', 'value' => $receipt_number, ), ), 'return' => 'ids', ) ); if ( empty( $orders ) ) { wp_die( esc_html__( 'Receipt not found.', 'afridirect-payment' ) ); } $order_id = $orders[0]; $order = wc_get_order( $order_id ); if ( ! $order ) { wp_die( esc_html__( 'Order not found.', 'afridirect-payment' ) ); } // Authorisation: a shop manager may always view; otherwise the requester must // own the order AND present the matching order key. $is_manager = current_user_can( 'manage_woocommerce' ); $owns_order = ( get_current_user_id() === $order->get_user_id() ); $key_is_valid = ( '' !== $order_key && hash_equals( (string) $order->get_order_key(), $order_key ) ); if ( ! $is_manager && ! ( $owns_order && $key_is_valid ) ) { wp_die( esc_html__( 'You do not have permission to view this receipt.', 'afridirect-payment' ) ); } // Display receipt. $this->display_receipt( $order, $receipt_number ); exit; } /** * Display receipt page. * * @param WC_Order $order Order object. * @param string $receipt_number Receipt number. */ private function display_receipt( $order, $receipt_number ) { include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/receipt.php'; } /** * Get receipt URL. * * @param string $receipt_number Receipt number. * @return string */ public function get_receipt_url( $receipt_number ) { $args = array( 'afridirect_receipt' => rawurlencode( $receipt_number ) ); // Attach the order key so the owner can open the link without an admin session. $orders = wc_get_orders( array( 'limit' => 1, // No non-meta way to look up an order by its generated receipt number. // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => '_afridirect_receipt_no', 'value' => $receipt_number, ), ), 'return' => 'ids', ) ); if ( ! empty( $orders ) ) { $order = wc_get_order( $orders[0] ); if ( $order ) { $args['afridirect_order_key'] = $order->get_order_key(); } } return add_query_arg( $args, home_url( '/' ) ); } }