mivita/tests/Feature/AboRecoveryAdvanceTest.php
Kevin 0660f4e0a0 Abo-Recovery: fehlgeschlagene Folgezahlung im Checkout nachholen
- 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>
2026-07-02 10:38:05 +00:00

113 lines
3.2 KiB
PHP

<?php
use App\Models\ShoppingOrder;
use App\Models\ShoppingPayment;
use App\Models\UserAbo;
use App\Models\UserAboOrder;
use App\Services\AboHelper;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(Tests\TestCase::class, RefreshDatabase::class);
function makeRecoveryAbo(): UserAbo
{
return UserAbo::create([
'user_id' => 1,
'member_id' => 1,
'shopping_user_id' => 1,
'auth_user_id' => 1,
'is_for' => 'me',
'email' => 'recovery-'.uniqid('', true).'@example.com',
'payone_userid' => 770100,
'clearingtype' => 'cc',
'active' => true,
'status' => 3,
'abo_interval' => 5,
'next_date' => now()->subDays(3)->toDateString(),
'last_date' => now()->subMonth()->toDateString(),
]);
}
function makeRecoveryPayment(UserAbo $abo): ShoppingPayment
{
$order = ShoppingOrder::create([
'shopping_user_id' => 1,
'auth_user_id' => 1,
'country_id' => 1,
'user_shop_id' => 1,
'recovery_user_abo_id' => $abo->id,
'is_abo' => true,
'abo_interval' => 5,
'paid' => false,
]);
return ShoppingPayment::create([
'shopping_order_id' => $order->id,
'reference' => uniqid('ref', true),
'clearingtype' => 'cc',
'amount' => 19.9,
'currency' => 'EUR',
'mode' => 'test',
'is_abo' => true,
'abo_interval' => 5,
]);
}
it('schreibt das bestehende Abo bei erfolgreicher Recovery-Zahlung fort, ohne ein neues anzulegen', function () {
$abo = makeRecoveryAbo();
$payment = makeRecoveryPayment($abo);
AboHelper::advanceAboAfterRecoveryPayment($payment);
$abo->refresh();
expect($abo->status)->toBe(2);
expect((bool) $abo->active)->toBeTrue();
expect(\Carbon\Carbon::parse($abo->next_date)->isFuture())->toBeTrue();
expect(UserAbo::count())->toBe(1);
expect(
UserAboOrder::where('shopping_order_id', $payment->shopping_order_id)
->where('user_abo_id', $abo->id)
->where('paid', true)
->exists()
)->toBeTrue();
});
it('ist idempotent und legt bei erneutem Aufruf keinen zweiten UserAboOrder an', function () {
$abo = makeRecoveryAbo();
$payment = makeRecoveryPayment($abo);
AboHelper::advanceAboAfterRecoveryPayment($payment);
AboHelper::advanceAboAfterRecoveryPayment($payment);
expect(UserAboOrder::where('shopping_order_id', $payment->shopping_order_id)->count())->toBe(1);
});
it('ignoriert Zahlungen ohne recovery_user_abo_id', function () {
$abo = makeRecoveryAbo();
$order = ShoppingOrder::create([
'shopping_user_id' => 1,
'auth_user_id' => 1,
'country_id' => 1,
'user_shop_id' => 1,
'is_abo' => true,
'abo_interval' => 5,
'paid' => false,
]);
$payment = ShoppingPayment::create([
'shopping_order_id' => $order->id,
'reference' => uniqid('ref', true),
'clearingtype' => 'cc',
'amount' => 19.9,
'currency' => 'EUR',
'mode' => 'test',
'is_abo' => true,
'abo_interval' => 5,
]);
AboHelper::advanceAboAfterRecoveryPayment($payment);
$abo->refresh();
expect($abo->status)->toBe(3);
expect(UserAboOrder::count())->toBe(0);
});