Depublikation: abgebrochener Checkout blockiert nicht mehr

Der Klick auf "Kostenpflichtig depublizieren" legte sofort einen
Pending-Kauf an; wer bei Stripe abbrach, sah danach dauerhaft "liegt
bereits ein Depublikations-Vorgang vor". Jetzt sperrt nur ein bezahlter
Vorgang — ein offener Pending-Kauf wird beim nächsten Klick
wiederverwendet (kein Duplikat), und die Detailseite zeigt das Angebot
weiterhin an.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-07 15:07:32 +00:00
parent 28de92e18b
commit 881a583bec
3 changed files with 50 additions and 13 deletions

View file

@ -94,7 +94,8 @@ test('the depublication checkout creates a pending purchase and hands it to stri
$checkout = Mockery::mock(Checkout::class);
$checkout->shouldReceive('toResponse')->andReturn(new RedirectResponse('https://checkout.stripe.com/c/pay/test'));
$this->mock(StripeCheckoutService::class, function ($mock) use ($checkout) {
$mock->shouldReceive('forDepublication')->once()->andReturn($checkout);
// Zweimal: der zweite Aufruf verwendet den offenen Kauf wieder.
$mock->shouldReceive('forDepublication')->twice()->andReturn($checkout);
});
$this->actingAs($user)
@ -107,10 +108,33 @@ test('the depublication checkout creates a pending purchase and hands it to stri
expect($purchase->price_cents)->toBe(19900);
expect($purchase->press_release_id)->toBe($release->id);
// Doppelbuchungs-Bremse: zweiter Aufruf legt keinen weiteren Kauf an.
// Abgebrochener Checkout blockiert nicht: erneuter Aufruf nutzt den
// Pending-Kauf wieder statt einen zweiten anzulegen.
$this->actingAs($user)
->get(route('me.checkout.depublication', $release->id))
->assertRedirect(route('me.bookings.index'));
->assertRedirect('https://checkout.stripe.com/c/pay/test');
expect(SinglePurchase::count())->toBe(1);
});
test('a paid depublication blocks a second checkout for the same release', function () {
/** @var TestCase $this */
$user = depublicationCustomer();
$release = publishedReleaseFor($user);
SinglePurchase::query()->create([
'user_id' => $user->id,
'type' => SinglePurchaseType::Depublication->value,
'status' => SinglePurchaseStatus::Paid->value,
'price_cents' => 19900,
'currency' => 'EUR',
'press_release_id' => $release->id,
]);
$this->actingAs($user)
->get(route('me.checkout.depublication', $release->id))
->assertRedirect(route('me.bookings.index'))
->assertSessionHas('checkout-notice');
expect(SinglePurchase::count())->toBe(1);
});
@ -145,12 +169,12 @@ test('the detail page hides the depublication offer without a price and after a
LivewireVolt::test('customer.press-releases.show', ['id' => $release->id])
->assertDontSee('Depublikation (Service)');
// Mit laufendem Vorgang: Hinweis statt erneutem Kauf-Button.
// Mit BEZAHLTEM Vorgang: Hinweis statt erneutem Kauf-Button.
config()->set('billing.depublication_price_cents', 19900);
SinglePurchase::query()->create([
$purchase = SinglePurchase::query()->create([
'user_id' => $user->id,
'type' => SinglePurchaseType::Depublication->value,
'status' => SinglePurchaseStatus::Pending->value,
'status' => SinglePurchaseStatus::Paid->value,
'price_cents' => 19900,
'currency' => 'EUR',
'press_release_id' => $release->id,
@ -158,6 +182,13 @@ test('the detail page hides the depublication offer without a price and after a
LivewireVolt::test('customer.press-releases.show', ['id' => $release->id])
->assertSee('Für diese Pressemitteilung liegt bereits ein Depublikations-Vorgang vor.')
->assertDontSee('Kostenpflichtig depublizieren');
// Ein offener Pending-Kauf (abgebrochener Checkout) blockiert das
// Angebot dagegen nicht — der Button bleibt nutzbar.
$purchase->update(['status' => SinglePurchaseStatus::Pending->value]);
LivewireVolt::test('customer.press-releases.show', ['id' => $release->id])
->assertSee('Kostenpflichtig depublizieren')
->assertDontSee('Für diese Pressemitteilung liegt bereits ein Depublikations-Vorgang vor.');
});
test('the paid webhook depublishes the release and the public page shows a tombstone', function () {