config[$key] ?? null; } }; } private function headers(): array { return [ 'PayPal-Auth-Algo' => 'SHA256withRSA', 'PayPal-Cert-Url' => 'https://api.paypal.com/cert.pem', 'PayPal-Transmission-Id' => 'transmission-1', 'PayPal-Transmission-Sig' => 'signature', 'PayPal-Transmission-Time' => '2026-08-10T08:00:00Z', ]; } public function test_it_verifies_a_sandbox_webhook_with_oauth(): void { Http::fake([ 'https://api-m.sandbox.paypal.com/v1/oauth2/token' => Http::response([ 'access_token' => 'access-token', ]), 'https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature' => Http::response([ 'verification_status' => 'SUCCESS', ]), ]); $verifier = $this->verifier([ 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'webhook_id' => 'WH-123', 'sandbox' => true, ]); $event = ['id' => 'WH-EVENT-1', 'event_type' => 'PAYMENT.CAPTURE.COMPLETED']; $this->assertTrue($verifier->verify($this->headers(), $event)); Http::assertSentCount(2); Http::assertSent(function (Request $request) use ($event) { return $request->url() === 'https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature' && $request['webhook_id'] === 'WH-123' && $request['transmission_id'] === 'transmission-1' && $request['webhook_event'] === $event && $request->hasHeader('Authorization', 'Bearer access-token'); }); } public function test_it_uses_the_live_api_when_sandbox_is_disabled(): void { Http::fake([ 'https://api-m.paypal.com/v1/oauth2/token' => Http::response(['access_token' => 'live-token']), 'https://api-m.paypal.com/v1/notifications/verify-webhook-signature' => Http::response([ 'verification_status' => 'FAILURE', ]), ]); $verifier = $this->verifier([ 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'webhook_id' => 'WH-LIVE', 'sandbox' => false, ]); $this->assertFalse($verifier->verify($this->headers(), ['id' => 'WH-EVENT-2'])); Http::assertSent(fn (Request $request) => str_starts_with($request->url(), 'https://api-m.paypal.com/')); } public function test_it_rejects_missing_signature_headers_without_calling_paypal(): void { Http::fake(); $verifier = $this->verifier([ 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'webhook_id' => 'WH-123', 'sandbox' => true, ]); $headers = $this->headers(); unset($headers['PayPal-Transmission-Sig']); $this->expectException(\InvalidArgumentException::class); try { $verifier->verify($headers, ['id' => 'WH-EVENT-3']); } finally { Http::assertNothingSent(); } } }