settings = Mockery::mock(StudioSettings::class); } private function partialGateway(): StripeGateway { return Mockery::mock(StripeGateway::class, [$this->settings]) ->makePartial() ->shouldAllowMockingProtectedMethods(); } private function payment(): Payment { return new Payment(5, 3, Payment::REG_LESSON, 12, 35.00, 'CAD', Payment::METHOD_CARD, Payment::STATUS_PENDING, id: 90); } public function testCreateIntentReturnsNullWhenNotConfigured(): void { $this->settings->shouldReceive('isStripeConfigured')->andReturn(false); $gateway = new StripeGateway($this->settings); self::assertNull($gateway->createIntent($this->payment())); } public function testCreateIntentSendsAmountInCentsAndReturnsIntent(): void { $this->settings->shouldReceive('isStripeConfigured')->andReturn(true); $intent = \Stripe\PaymentIntent::constructFrom(['id' => 'pi_1', 'client_secret' => 'cs_1']); $gateway = $this->partialGateway(); $gateway->shouldReceive('paymentIntentsCreate') ->once() ->with( Mockery::on(static fn (array $p): bool => $p['amount'] === 3500 && $p['currency'] === 'cad' && $p['metadata']['payment_id'] === '90'), Mockery::on(static fn (array $o): bool => $o['idempotency_key'] === 'usc-payment-90') ) ->andReturn($intent); self::assertSame('pi_1', $gateway->createIntent($this->payment())->id); } public function testCreateIntentReturnsNullOnStripeError(): void { $this->settings->shouldReceive('isStripeConfigured')->andReturn(true); $gateway = $this->partialGateway(); $gateway->shouldReceive('paymentIntentsCreate')->once()->andThrow(new \RuntimeException('declined')); self::assertNull($gateway->createIntent($this->payment())); } public function testVerifyWebhookReturnsNullWithoutSecret(): void { $this->settings->shouldReceive('webhookSecret')->andReturn(''); $gateway = new StripeGateway($this->settings); self::assertNull($gateway->verifyWebhook('{}', 'sig')); } public function testVerifyWebhookReturnsNullWithoutSignatureHeader(): void { $this->settings->shouldReceive('webhookSecret')->andReturn('whsec_123'); $gateway = new StripeGateway($this->settings); self::assertNull($gateway->verifyWebhook('{}', '')); } public function testVerifyWebhookReturnsNullOnInvalidSignature(): void { $this->settings->shouldReceive('webhookSecret')->andReturn('whsec_123'); $gateway = $this->partialGateway(); $gateway->shouldReceive('constructEvent')->once()->andThrow(new \RuntimeException('bad signature')); self::assertNull($gateway->verifyWebhook('{}', 'sig')); } public function testVerifyWebhookReturnsEventOnSuccess(): void { $this->settings->shouldReceive('webhookSecret')->andReturn('whsec_123'); $event = \Stripe\Event::constructFrom(['type' => 'payment_intent.succeeded']); $gateway = $this->partialGateway(); $gateway->shouldReceive('constructEvent')->once()->with('{payload}', 'sig', 'whsec_123')->andReturn($event); self::assertSame('payment_intent.succeeded', $gateway->verifyWebhook('{payload}', 'sig')->type); } }