From 3c0e7afeb258680883a96254415b02ae73134e69 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 2 Jan 2026 22:24:52 +0100 Subject: [PATCH] Link existing Paddle IDs --- .beads/issues.jsonl | 4 +-- app/Filament/Resources/PackageResource.php | 38 +++++++++++++++++++++- app/Models/Package.php | 10 ++++++ tests/Unit/PackagePaddleLinkTest.php | 31 ++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 tests/Unit/PackagePaddleLinkTest.php diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 119733c..a1d16b1 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -24,7 +24,7 @@ {"id":"fotospiel-app-64l","title":"SEC-FE-01 CSP nonce/hashing rollout","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:54:47.607047443+01:00","created_by":"soeren","updated_at":"2026-01-01T15:55:56.477104351+01:00","closed_at":"2026-01-01T15:55:56.477104351+01:00","close_reason":"Completed in codebase (verified) - duplicate of fotospiel-app-zli"} {"id":"fotospiel-app-6dp","title":"Coupon ops enhancements (redemption service, preview endpoint, widget, export)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:09:09.275919717+01:00","created_by":"soeren","updated_at":"2026-01-01T16:09:14.882264149+01:00","closed_at":"2026-01-01T16:09:14.882264149+01:00","close_reason":"Completed in codebase (verified)"} {"id":"fotospiel-app-6oj","title":"Security review: media pipeline code audit (AV/EXIF, signed URLs, storage separation)","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T16:05:31.390878341+01:00","created_by":"soeren","updated_at":"2026-01-01T16:05:31.390878341+01:00"} -{"id":"fotospiel-app-6yt","title":"Paddle migration: register sandbox webhooks + document events consumed","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T15:56:34.333714988+01:00","created_by":"soeren","updated_at":"2026-01-01T15:56:34.333714988+01:00"} +{"id":"fotospiel-app-6yt","title":"Paddle migration: register sandbox webhooks + document events consumed","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:56:34.333714988+01:00","created_by":"soeren","updated_at":"2026-01-02T22:23:52.212191068+01:00","closed_at":"2026-01-02T22:23:52.212191068+01:00","close_reason":"Completed"} {"id":"fotospiel-app-7bu","title":"Paddle migration: extend config/env handling for Paddle keys/webhook secrets","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:57:27.242854801+01:00","created_by":"soeren","updated_at":"2026-01-01T15:57:32.890355888+01:00","closed_at":"2026-01-01T15:57:32.890355888+01:00","close_reason":"Completed in codebase (verified)"} {"id":"fotospiel-app-7u1","title":"Paddle catalog sync: PaddlePackagePull job","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:00:47.468892178+01:00","created_by":"soeren","updated_at":"2026-01-01T16:00:53.126602817+01:00","closed_at":"2026-01-01T16:00:53.126602817+01:00","close_reason":"Completed in codebase (verified)"} {"id":"fotospiel-app-95m","title":"Paddle migration: admin catalog sync UI for packages","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:57:49.790409261+01:00","created_by":"soeren","updated_at":"2026-01-01T15:57:55.418180246+01:00","closed_at":"2026-01-01T15:57:55.418180246+01:00","close_reason":"Completed in codebase (verified)"} @@ -80,7 +80,7 @@ {"id":"fotospiel-app-mx5","title":"Localized SEO: sitemap updated with locale alternates","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:02:15.177013722+01:00","created_by":"soeren","updated_at":"2026-01-01T16:02:20.812287917+01:00","closed_at":"2026-01-01T16:02:20.812287917+01:00","close_reason":"Completed in codebase (verified)"} {"id":"fotospiel-app-mxw","title":"Security review: configure env assumptions for dynamic testing","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T16:04:29.498402235+01:00","created_by":"soeren","updated_at":"2026-01-01T16:04:29.498402235+01:00"} {"id":"fotospiel-app-n8q","title":"Paddle migration: draft production cutover procedure","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T15:56:51.427425262+01:00","created_by":"soeren","updated_at":"2026-01-01T15:56:51.427425262+01:00"} -{"id":"fotospiel-app-nfi","title":"Paddle catalog sync: add Link existing Paddle entity action in admin","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T15:59:09.164334657+01:00","created_by":"soeren","updated_at":"2026-01-01T15:59:09.164334657+01:00"} +{"id":"fotospiel-app-nfi","title":"Paddle catalog sync: add Link existing Paddle entity action in admin","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:59:09.164334657+01:00","created_by":"soeren","updated_at":"2026-01-02T22:15:15.030896509+01:00","closed_at":"2026-01-02T22:15:15.030896509+01:00","close_reason":"Completed"} {"id":"fotospiel-app-niv","title":"Paddle catalog sync: Package model casts/fillable + factory","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:00:13.646318173+01:00","created_by":"soeren","updated_at":"2026-01-01T16:00:19.296543136+01:00","closed_at":"2026-01-01T16:00:19.296543136+01:00","close_reason":"Completed in codebase (verified)"} {"id":"fotospiel-app-o4n","title":"Audit PayPal SDK migration doc vs code (PayPal integration missing)","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T16:07:34.316575518+01:00","created_by":"soeren","updated_at":"2026-01-01T16:07:34.316575518+01:00"} {"id":"fotospiel-app-o96","title":"Paddle catalog sync: seed sandbox catalog via MCP","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:59:37.63819424+01:00","created_by":"soeren","updated_at":"2026-01-02T21:05:42.225830987+01:00","closed_at":"2026-01-02T21:05:42.225830987+01:00","close_reason":"Not needed"} diff --git a/app/Filament/Resources/PackageResource.php b/app/Filament/Resources/PackageResource.php index acaea37..3e68f76 100644 --- a/app/Filament/Resources/PackageResource.php +++ b/app/Filament/Resources/PackageResource.php @@ -276,7 +276,7 @@ class PackageResource extends Resource ->colors([ 'success' => 'synced', 'warning' => 'syncing', - 'info' => 'dry-run', + 'info' => ['dry-run', 'linked', 'pulled'], 'danger' => ['failed', 'pull-failed'], ]) ->formatStateUsing(fn ($state) => $state ? Str::headline($state) : null) @@ -316,6 +316,42 @@ class PackageResource extends Resource ->body('Das Paket wird im Hintergrund mit Paddle abgeglichen.') ->send(); }), + Actions\Action::make('linkPaddle') + ->label('Paddle verknüpfen') + ->icon('heroicon-o-link') + ->color('info') + ->form([ + TextInput::make('paddle_product_id') + ->label('Paddle Produkt-ID') + ->required() + ->maxLength(191), + TextInput::make('paddle_price_id') + ->label('Paddle Preis-ID') + ->required() + ->maxLength(191), + ]) + ->fillForm(fn (Package $record) => [ + 'paddle_product_id' => $record->paddle_product_id, + 'paddle_price_id' => $record->paddle_price_id, + ]) + ->action(function (Package $record, array $data): void { + $record->linkPaddleIds($data['paddle_product_id'], $data['paddle_price_id']); + + PullPackageFromPaddle::dispatch($record->id); + + app(SuperAdminAuditLogger::class)->recordModelMutation( + 'linked', + $record, + SuperAdminAuditLogger::fieldsMetadata($data), + static::class + ); + + Notification::make() + ->success() + ->title('Paddle-Verknüpfung gespeichert') + ->body('Die IDs wurden gespeichert und ein Pull wurde angestoßen.') + ->send(); + }), Actions\Action::make('pullPaddle') ->label('Status von Paddle holen') ->icon('heroicon-o-cloud-arrow-down') diff --git a/app/Models/Package.php b/app/Models/Package.php index eff9308..e839764 100644 --- a/app/Models/Package.php +++ b/app/Models/Package.php @@ -152,6 +152,16 @@ class Package extends Model return is_string($message) && $message !== '' ? $message : null; } + public function linkPaddleIds(string $productId, string $priceId): void + { + $this->forceFill([ + 'paddle_product_id' => $productId, + 'paddle_price_id' => $priceId, + 'paddle_sync_status' => 'linked', + 'paddle_synced_at' => now(), + ])->save(); + } + public function getActivatesImmediatelyAttribute(): bool { // Default: Pakete werden nach Kauf sofort freigeschaltet (digitale Dienstleistung). diff --git a/tests/Unit/PackagePaddleLinkTest.php b/tests/Unit/PackagePaddleLinkTest.php new file mode 100644 index 0000000..db125d4 --- /dev/null +++ b/tests/Unit/PackagePaddleLinkTest.php @@ -0,0 +1,31 @@ +create([ + 'paddle_product_id' => null, + 'paddle_price_id' => null, + 'paddle_sync_status' => null, + 'paddle_synced_at' => null, + ]); + + $package->linkPaddleIds('pro_123', 'pri_123'); + + $package->refresh(); + + $this->assertSame('pro_123', $package->paddle_product_id); + $this->assertSame('pri_123', $package->paddle_price_id); + $this->assertSame('linked', $package->paddle_sync_status); + $this->assertNotNull($package->paddle_synced_at); + } +}