Link existing Paddle IDs

This commit is contained in:
Codex Agent
2026-01-02 22:24:52 +01:00
parent bb67d68eba
commit 3c0e7afeb2
4 changed files with 80 additions and 3 deletions

View File

@@ -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"}

View File

@@ -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')

View File

@@ -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).

View File

@@ -0,0 +1,31 @@
<?php
namespace Tests\Unit;
use App\Models\Package;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PackagePaddleLinkTest extends TestCase
{
use RefreshDatabase;
public function test_link_paddle_ids_updates_fields(): void
{
$package = Package::factory()->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);
}
}