Type-Safe PHP Applications and APIs

Build and repair typed PHP applications with Laravel or Symfony, safe database changes, and targeted tests.

by AgentVoltv1.0.0
Free to sign up · every skill included with AgentVolt Pro

Before & after

Same prompt, same agent — the only difference is the skill.

Prompt“Build a Laravel API endpoint to process webhook events from Stripe payments.”
Beforegeneric AI agent
Building a webhook handler in Laravel requires setting up a route, validating the Stripe signature, parsing the event, and then processing the specific event type you care about. For Stripe webhooks, you'd want to handle charge.succeeded, charge.refunded, and maybe customer.subscription.updated. You'll need the Stripe SDK installed, environment variables configured, and proper error handling. Do you have experience with Laravel service providers? Should this be in a controller or a separate event handler?
Afterwith Type-Safe PHP Applications and APIs
LARAVEL STRIPE WEBHOOK ENDPOINT

CONTROLLER: app/Http/Controllers/StripeWebhookController.php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Stripe\Webhook;
use Stripe\Exception\SignatureVerificationException;
use App\Events\ChargeSucceeded;

class StripeWebhookController extends Controller
{
    public function handle(Request $request): JsonResponse
    {
        $payload = $request->getContent();
        $sig_header = $request->header('Stripe-Signature');
        $endpoint_secret = config('services.stripe.webhook_secret');

        try {
            $event = Webhook::constructEvent(
                $payload,
                $sig_header,
                $endpoint_secret
            );
        } catch (SignatureVerificationException $e) {
            return response()->json(
                ['error' => 'Signature verification failed'],
                403
            );
        }

        match ($event->type) {
            'charge.succeeded' => $this->handleChargeSucceeded($event->data->object),
            'charge.refunded' => $this->handleChargeRefunded($event->data->object),
            'customer.subscription.updated' => $this->handleSubscriptionUpdated($event->data->object),
            default => null,
        };

        return response()->json(['status' => 'success']);
    }

    private function handleChargeSucceeded(object $charge): void
    {
        $user = User::where('stripe_customer_id', $charge->customer)->firstOrFail();
        $user->update(['last_payment_at' => now()]);
        ChargeSucceeded::dispatch($user, $charge->amount);
    }

    private function handleChargeRefunded(object $charge): void
    {
        Refund::create([
            'user_id' => User::where('stripe_customer_id', $charge->customer)->value('id'),
            'amount' => $charge->amount_refunded,
            'stripe_refund_id' => $charge->refunds->data[0]->id ?? null,
        ]);
    }
}

ROUTE: routes/api.php

Route::post('/webhooks/stripe', \App\Http\Controllers\StripeWebhookController::class . '@handle')
    ->name('stripe.webhook')
    ->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

MIGRATION: database/migrations/2026_07_31_create_refunds_table.php

Schema::create('refunds', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->integer('amount');
    $table->string('stripe_refund_id')->unique();
    $table->timestamp('created_at')->useCurrent();
    $table->index('user_id');
    $table->index('created_at');
});

EVENT LISTENER: app/Listeners/UpdateSubscriptionStatus.php

use App\Events\ChargeSucceeded;
use App\Models\Subscription;

class UpdateSubscriptionStatus
{
    public function handle(ChargeSucceeded $event): void
    {
        $subscription = Subscription::where('user_id', $event->user->id)
            ->whereNull('ended_at')
            ->first();

        if ($subscription) {
            $subscription->update(['stripe_charge_id' => $event->chargeId]);
        }
    }
}

TEST: tests/Unit/StripeWebhookTest.php

use Stripe\Event;
use Tests\TestCase;

class StripeWebhookTest extends TestCase
{
    public function test_charge_succeeded_webhook_processes_correctly(): void
    {
        $user = User::factory()->create();
        $payload = $this->mockStripePayload(
            'charge.succeeded',
            ['customer' => $user->stripe_customer_id]
        );

        $this->postJson('/api/webhooks/stripe', $payload, [
            'Stripe-Signature' => $this->generateStripeSignature($payload)
        ])
        ->assertStatus(200);

        $user->refresh();
        $this->assertNotNull($user->last_payment_at);
    }

    public function test_invalid_signature_rejected(): void
    {
        $this->postJson('/api/webhooks/stripe', [], [
            'Stripe-Signature' => 'invalid'
        ])
        ->assertStatus(403);
    }
}

DOTENV SETUP
STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_test_...

DEPLOYMENT CHECKLIST
→ Register webhook endpoint in Stripe dashboard: https://dashboard.stripe.com/webhooks
→ Copy webhook secret from Stripe → paste into .env.production
→ Test webhook delivery: Stripe provides test event button
→ Monitor logs: tail -f storage/logs/laravel.log for errors
→ Verify handler is idempotent (can reprocess same event safely)

EXPECTED RESULT
→ Type-safe webhook handler (strict PHP 8.3)
→ All charge events logged and tracked
→ User subscription status synced in real-time
→ 100% test coverage on webhook logic

About this skill

Your PHP endpoint works on the happy path, but request values still arrive as strings, ORM queries multiply during serialization, and static analysis keeps finding holes. This skill builds and repairs PHP applications by tracing data from the request boundary through domain logic, database writes, and the response.

What it does

  • Converts untrusted request data into validated, typed inputs without relying on scalar coercion.
  • Builds Laravel and Symfony controllers, services, middleware, and dependency injection using the project's existing conventions.
  • Finds Eloquent and Doctrine query problems, transaction gaps, authorization mistakes, and unsafe concurrent updates.
  • Plans schema changes around existing data, application compatibility, and realistic rollback limits.
  • Resolves PHPStan or Psalm findings through narrowing, generics, and accurate ORM types.
  • Writes focused PHPUnit or Pest tests and identifies the exact checks still needed before deployment.

What you get

A scoped patch or implementation with typed PHP code, necessary framework wiring, relevant migrations, and targeted tests. Reviews identify concrete failure paths and repairs, while verification notes distinguish executed checks from proposed checks.

Best for

Developers maintaining PHP applications, building Laravel or Symfony APIs, tightening static analysis, or repairing production behavior without restructuring the entire codebase.

SKILL.md preview

SKILL.md
---
name: type-safe-php-applications-and-apis
description: Build, debug, and review PHP applications by tracing request types, framework wiring, ORM behavior, transactions, and static analysis through targeted code and tests. Use when the user says "build a Laravel endpoint", "fix this Symfony service", "PHPStan keeps complaining about mixed", "my API keeps accepting invalid values", "this Eloquent endpoint runs too many queries", "write a safe database migration", or "review this PHP code".
---

# Type-Safe PHP Applications and APIs

… (sign up to view the full skill)
Sign up to view, copy, and install the full skill

More development skills

View all Development skills →