stripe_manager = $stripe_manager; $this->purchase_repository = $purchase_repository; $this->secure_token = $secure_token; } public function register(): void { add_action( 'init', array( $this, 'maybe_handle_webhook' ) ); } public function maybe_handle_webhook(): void { if ( ! isset( $_GET[ Constants::STRIPE_WEBHOOK_QUERY_VAR ] ) ) { return; } $payload = file_get_contents( 'php://input' ); $signature = isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ) : ''; if ( ! $this->verify_signature( $payload, $signature ) ) { status_header( 400 ); exit; } $event = json_decode( $payload, true ); if ( ! is_array( $event ) || empty( $event['type'] ) ) { status_header( 400 ); exit; } if ( 'checkout.session.completed' === $event['type'] ) { $this->handle_checkout_completed( $event ); } status_header( 200 ); exit; } private function handle_checkout_completed( array $event ): void { $session = $event['data']['object'] ?? array(); if ( empty( $session['payment_status'] ) || 'paid' !== $session['payment_status'] ) { return; } if ( empty( $session['id'] ) ) { return; } $article_id = ! empty( $session['metadata']['article_id'] ) ? absint( $session['metadata']['article_id'] ) : absint( $session['client_reference_id'] ?? 0 ); if ( ! $article_id ) { return; } $purchase = $this->purchase_repository->find_by_session_id( sanitize_text_field( $session['id'] ) ); if ( ! $purchase ) { return; } if ( (int) $purchase['article_id'] !== $article_id || (int) $purchase['amount'] !== (int) $session['amount_total'] || strtolower( $purchase['currency'] ) !== strtolower( $session['currency'] ) ) { return; } if ( 'paid' === $purchase['status'] ) { return; } $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 ); $this->purchase_repository->mark_paid( sanitize_text_field( $session['id'] ), sanitize_text_field( $session['payment_intent'] ?? '' ), $article_id, $email, absint( $session['amount_total'] ?? 0 ), sanitize_text_field( $session['currency'] ?? 'usd' ), $token_hash ); } private function verify_signature( string $payload, string $signature_header ): bool { $secret = $this->stripe_manager->get_webhook_secret(); if ( '' === $secret || '' === $signature_header ) { return false; } $timestamp = ''; $signature = ''; foreach ( explode( ',', $signature_header ) as $part ) { $pieces = explode( '=', $part, 2 ); if ( 2 !== count( $pieces ) ) { continue; } if ( 't' === $pieces[0] ) { $timestamp = $pieces[1]; } if ( 'v1' === $pieces[0] ) { $signature = $pieces[1]; } } if ( '' === $timestamp || '' === $signature ) { return false; } if ( abs( time() - (int) $timestamp ) > 300 ) { return false; } $expected = hash_hmac( 'sha256', $timestamp . '.' . $payload, $secret ); return hash_equals( $expected, $signature ); } }