- Pay-now-Flow: bei fehlgeschlagenem Abo (Status 3) begleicht der Inhaber die offene Rechnung im Standard-Checkout mit frei waehlbarer, abo-faehiger Zahlart (nur eigenes Abo: Berater me-View + Kundenportal), payNow-Controller + Routen - AboRecoveryCheckoutService baut die ShoppingInstance aus den Abo-Artikeln; neue Spalte shopping_orders.recovery_user_abo_id verknuepft Bestellung<->Abo - Bei Erfolg: AboHelper::advanceAboAfterRecoveryPayment schreibt das Abo fort (Status->2, next_date) und uebernimmt die gewaehlte Zahlart als neues Token fuer kuenftige Abbuchungen - Neue Pay-now-Fehlermail (AboPaymentFailedMail) ersetzt die generische Fehlermail im Abo-Cron; Link fuehrt auf die Abo-Detailseite (CRM bzw. Portal) - Retry-/"extern erledigt"-Buttons nur noch fuer Admins; tote user/portal- Retry-Routen entfernt - Checkout: Recovery-Hinweis statt "Abschluss des Abonnements", Lieferadresse vorbelegt, korrekter Zurueck-Link (Abo statt Shop) - UserAbo::selfServiceDetailUrl zentralisiert die Abo-URL (Mail, Back-Links) - Test-Command abo:test-recovery; Tests fuer payNow, Advance-Logik, Fehlermail Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
3 KiB
PHP
90 lines
3 KiB
PHP
<?php
|
|
|
|
use App\Http\Controllers\User\AboController;
|
|
use App\Models\UserAbo;
|
|
use App\Repositories\AboRepository;
|
|
use App\Services\AboRecoveryCheckoutService;
|
|
use App\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
use function Pest\Laravel\mock;
|
|
|
|
uses(Tests\TestCase::class, RefreshDatabase::class);
|
|
|
|
function makeRetryOwnerAbo(int $status = 3, bool $active = true): array
|
|
{
|
|
$owner = User::forceCreate([
|
|
'email' => 'owner-'.uniqid('', true).'@example.com',
|
|
'password' => bcrypt('secret'),
|
|
'lang' => 'de',
|
|
]);
|
|
|
|
$abo = UserAbo::create([
|
|
'user_id' => $owner->id,
|
|
'member_id' => $owner->id,
|
|
'shopping_user_id' => 1,
|
|
'is_for' => 'me',
|
|
'email' => $owner->email,
|
|
'payone_userid' => 900300,
|
|
'clearingtype' => 'cc',
|
|
'active' => $active,
|
|
'status' => $status,
|
|
'abo_interval' => 5,
|
|
'next_date' => now()->toDateString(),
|
|
]);
|
|
|
|
return ['owner' => $owner, 'abo' => $abo];
|
|
}
|
|
|
|
it('startet den Recovery-Checkout für das eigene fehlgeschlagene Abo und leitet dorthin weiter', function () {
|
|
$fixture = makeRetryOwnerAbo();
|
|
$this->actingAs($fixture['owner']);
|
|
|
|
mock(AboRecoveryCheckoutService::class)
|
|
->shouldReceive('buildCheckoutUrl')
|
|
->once()
|
|
->andReturn('https://checkout.test/checkout/card/abc123');
|
|
|
|
$controller = new AboController(app(AboRepository::class));
|
|
$response = $controller->payNow('me', $fixture['abo']->id, app(AboRecoveryCheckoutService::class));
|
|
|
|
expect($response->getTargetUrl())->toBe('https://checkout.test/checkout/card/abc123');
|
|
});
|
|
|
|
it('verhindert die nachträgliche Zahlung für ein fremdes Abo (403)', function () {
|
|
$fixture = makeRetryOwnerAbo();
|
|
|
|
$stranger = User::forceCreate([
|
|
'email' => 'stranger-'.uniqid('', true).'@example.com',
|
|
'password' => bcrypt('secret'),
|
|
'lang' => 'de',
|
|
]);
|
|
$this->actingAs($stranger);
|
|
|
|
$service = mock(AboRecoveryCheckoutService::class);
|
|
$service->shouldNotReceive('buildCheckoutUrl');
|
|
|
|
$controller = new AboController(app(AboRepository::class));
|
|
|
|
expect(fn () => $controller->payNow('me', $fixture['abo']->id, $service))
|
|
->toThrow(HttpException::class);
|
|
});
|
|
|
|
it('lehnt die nachträgliche Zahlung ab, wenn das Abo nicht fehlgeschlagen ist', function () {
|
|
Route::post('/user/abos/detail/{view}/{id}', fn () => '')->name('user_abos_detail');
|
|
app('router')->getRoutes()->refreshNameLookups();
|
|
|
|
$fixture = makeRetryOwnerAbo(status: 2);
|
|
$this->actingAs($fixture['owner']);
|
|
|
|
$service = mock(AboRecoveryCheckoutService::class);
|
|
$service->shouldNotReceive('buildCheckoutUrl');
|
|
|
|
$controller = new AboController(app(AboRepository::class));
|
|
$response = $controller->payNow('me', $fixture['abo']->id, $service);
|
|
|
|
expect($response->getTargetUrl())->toContain('/user/abos/detail/me/'.$fixture['abo']->id);
|
|
expect(session('alert-error'))->toBe(__('abo.pay_now_not_allowed'));
|
|
});
|