stripe_manager = $stripe_manager; $this->stripe_session = $stripe_session; $this->purchase_repository = $purchase_repository; $this->access_manager = $access_manager; $this->secure_token = $secure_token; } public function register(): void { add_action( 'admin_post_' . Constants::STRIPE_CHECKOUT_ACTION, array( $this, 'handle_checkout_request' ) ); add_action( 'admin_post_nopriv_' . Constants::STRIPE_CHECKOUT_ACTION, array( $this, 'handle_checkout_request' ) ); add_action( 'template_redirect', array( $this, 'maybe_handle_payment_return' ) ); add_filter( 'allowed_redirect_hosts', array( $this, 'allow_stripe_redirect_host' ) ); } public function handle_checkout_request(): void { $article_id = isset( $_POST['articlevault_article_id'] ) ? absint( $_POST['articlevault_article_id'] ) : 0; $nonce = isset( $_POST['articlevault_checkout_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['articlevault_checkout_nonce'] ) ) : ''; if ( ! $article_id || ! wp_verify_nonce( $nonce, 'articlevault_checkout_' . $article_id ) ) { wp_die( esc_html__( 'Security check failed.', 'aibender-articlevault-pro' ) ); } if ( 'publish' !== get_post_status( $article_id ) ) { wp_die( esc_html__( 'Invalid article.', 'aibender-articlevault-pro' ) ); } if ( ! $this->stripe_manager->is_configured() ) { wp_die( esc_html__( 'Stripe is not configured.', 'aibender-articlevault-pro' ) ); } $email = isset( $_POST['articlevault_email'] ) ? sanitize_email( wp_unslash( $_POST['articlevault_email'] ) ) : ''; $session = $this->stripe_session->create_checkout_session( $article_id, $email ); if ( is_wp_error( $session ) ) { wp_die( esc_html( $session->get_error_message() ) ); } if ( empty( $session['id'] ) || empty( $session['url'] ) ) { wp_die( esc_html__( 'Unable to create Stripe Checkout session.', 'aibender-articlevault-pro' ) ); } $this->purchase_repository->create_pending_purchase( sanitize_text_field( $session['id'] ), $article_id, $email, $this->stripe_manager->get_price_cents(), $this->stripe_manager->get_currency() ); $this->redirect_to_stripe( $session['url'] ); } public function maybe_handle_payment_return(): void { $payment_status = isset( $_GET['articlevault_payment'] ) ? sanitize_key( wp_unslash( $_GET['articlevault_payment'] ) ) : ''; if ( 'cancelled' === $payment_status ) { $this->render_cancel_page(); } if ( 'success' !== $payment_status ) { return; } $this->handle_success_return(); } private function handle_success_return(): void { $session_id = isset( $_GET['session_id'] ) ? sanitize_text_field( wp_unslash( $_GET['session_id'] ) ) : ''; if ( '' === $session_id ) { $this->render_error_page( __( 'The Stripe session ID is missing.', 'aibender-articlevault-pro' ) ); } $session = $this->stripe_session->retrieve_checkout_session( $session_id ); if ( is_wp_error( $session ) ) { $this->render_error_page( __( 'We could not verify this payment.', 'aibender-articlevault-pro' ) ); } if ( empty( $session['payment_status'] ) || 'paid' !== $session['payment_status'] ) { $this->render_error_page( __( 'This payment has not been completed.', 'aibender-articlevault-pro' ) ); } $purchase = $this->purchase_repository->find_by_session_id( $session_id ); if ( ! $purchase ) { $this->render_error_page( __( 'The matching purchase could not be found.', 'aibender-articlevault-pro' ) ); } $article_id = ! empty( $session['metadata']['article_id'] ) ? absint( $session['metadata']['article_id'] ) : absint( $session['client_reference_id'] ?? 0 ); $amount = absint( $session['amount_total'] ?? 0 ); $currency = strtolower( sanitize_text_field( $session['currency'] ?? '' ) ); if ( ! $article_id || (int) $purchase['article_id'] !== $article_id || (int) $purchase['amount'] !== $amount || strtolower( (string) $purchase['currency'] ) !== $currency ) { $this->render_error_page( __( 'The payment details did not match this purchase.', 'aibender-articlevault-pro' ) ); } if ( 'publish' !== get_post_status( $article_id ) ) { $this->render_error_page( __( 'The purchased article is no longer available.', 'aibender-articlevault-pro' ) ); } $email = ''; if ( ! empty( $session['customer_details']['email'] ) ) { $email = sanitize_email( $session['customer_details']['email'] ); } elseif ( ! empty( $session['customer_email'] ) ) { $email = sanitize_email( $session['customer_email'] ); } $raw_token = $this->secure_token->generate(); $token_hash = $this->secure_token->hash( $raw_token ); if ( 'paid' !== (string) $purchase['status'] ) { $this->purchase_repository->mark_paid( $session_id, sanitize_text_field( $session['payment_intent'] ?? '' ), $article_id, $email, $amount, $currency, $token_hash ); } if ( ! $this->purchase_repository->claim_access_token( $session_id, $token_hash ) ) { if ( $this->access_manager->user_has_access( $article_id ) ) { $this->render_success_page( get_permalink( $article_id ) ); } $this->render_error_page( __( 'This payment return has already been used. Please open the article in the browser where the purchase was completed.', 'aibender-articlevault-pro' ) ); } $this->access_manager->grant_access( $article_id, $raw_token ); $this->render_success_page( get_permalink( $article_id ) ); } private function render_success_page( string $redirect_url ): void { $this->render_status_page( __( 'Payment successful', 'aibender-articlevault-pro' ), __( 'Thank you for your purchase. Your article is now unlocked.', 'aibender-articlevault-pro' ), $redirect_url, __( 'Continue to article', 'aibender-articlevault-pro' ), true ); } private function render_cancel_page(): void { $article_id = get_queried_object_id(); $return_url = $article_id ? get_permalink( $article_id ) : home_url( '/' ); $this->render_status_page( __( 'Payment cancelled', 'aibender-articlevault-pro' ), __( 'No payment was taken. You can return to the article and try again.', 'aibender-articlevault-pro' ), $return_url, __( 'Return to article', 'aibender-articlevault-pro' ), false ); } private function render_error_page( string $message ): void { $article_id = get_queried_object_id(); $return_url = $article_id ? get_permalink( $article_id ) : home_url( '/' ); $this->render_status_page( __( 'Payment verification failed', 'aibender-articlevault-pro' ), $message, $return_url, __( 'Return to article', 'aibender-articlevault-pro' ), false ); } private function render_status_page( string $title, string $message, string $button_url, string $button_text, bool $auto_redirect ): void { nocache_headers(); status_header( 200 ); $safe_button_url = esc_url( $button_url ); ?> >