- Karten-Update ohne Abbuchung via PAYONE creditcardcheck/pseudocardpan (AboPaymentMethodService, CardUpdateResult/CreditCardCheckData-DTOs) - Eingabe nur durch Abo-Inhaber: CRM- und Portal-Formular + zeitlich begrenzter Berater→Kunde-Link (AboCardUpdateLink + Mail) - Änderungshistorie in user_abo_card_changes, Anzeige im Admin-Abo-Detail - Admin: Abo löschen (destroy) inkl. Test - Portal-Fix: myOrderCreate wirft 404 statt TypeError bei ungültiger ID - VIP-Sichtbarkeitstests für Einmalartikel-Fenster ergänzt - Enthält bereits die Freeze-Guards für den Stripe-Notbetrieb (Folge-Commit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
127 lines
4.3 KiB
PHP
127 lines
4.3 KiB
PHP
<?php
|
|
|
|
use App\Http\Controllers\Portal\AboController;
|
|
use App\Models\Customer;
|
|
use App\Models\ShoppingUser;
|
|
use App\Models\UserAbo;
|
|
use App\Services\AboPaymentMethodService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\URL;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
function stubMissingRoutes(): void
|
|
{
|
|
URL::resolveMissingNamedRoutesUsing(
|
|
fn (string $name, array $parameters) => '/'.$name.'/'.implode('/', $parameters)
|
|
);
|
|
}
|
|
|
|
function makePortalAboFixture(array $cardOverride = []): array
|
|
{
|
|
$shoppingUser = ShoppingUser::create([
|
|
'auth_user_id' => 1,
|
|
'billing_firstname' => 'Karl',
|
|
'billing_lastname' => 'Kunde',
|
|
'billing_address' => 'Kundenweg 2',
|
|
'billing_zipcode' => '11111',
|
|
'billing_city' => 'Kundenstadt',
|
|
'billing_country_id' => 1,
|
|
'shipping_country_id' => 1,
|
|
'billing_email' => 'kunde@example.com',
|
|
'same_as_billing' => true,
|
|
'mode' => 'test',
|
|
'wp_order_number' => 0,
|
|
]);
|
|
|
|
$abo = UserAbo::create([
|
|
'user_id' => 1,
|
|
'shopping_user_id' => $shoppingUser->id,
|
|
'email' => 'kunde@example.com',
|
|
'clearingtype' => 'cc',
|
|
'wallettype' => '',
|
|
'payone_userid' => 111,
|
|
'carddata' => array_merge([
|
|
'pseudocardpan' => 'OLD_PSEUDO',
|
|
'truncatedcardpan' => '411111XXXXXX0000',
|
|
'cardexpiredate' => '2501',
|
|
], $cardOverride),
|
|
'abo_interval' => 5,
|
|
'status' => 2,
|
|
'card_update_pending_at' => now(),
|
|
]);
|
|
|
|
$customer = Customer::create([
|
|
'name' => 'Karl Kunde',
|
|
'email' => 'kunde@example.com',
|
|
'shopping_user_id' => $shoppingUser->id,
|
|
]);
|
|
|
|
return compact('shoppingUser', 'abo', 'customer');
|
|
}
|
|
|
|
it('formats the masked card from token data only', function () {
|
|
$abo = new UserAbo([
|
|
'clearingtype' => 'cc',
|
|
'carddata' => ['truncatedcardpan' => '411111XXXXXX1234', 'cardexpiredate' => '3012'],
|
|
]);
|
|
|
|
expect($abo->maskedCard())->toBe('411111XXXXXX1234 (12/30)');
|
|
|
|
$noToken = new UserAbo(['clearingtype' => 'cc', 'carddata' => ['pseudocardpan' => 'X']]);
|
|
expect($noToken->maskedCard())->toBeNull();
|
|
});
|
|
|
|
it('renders the portal credit-card form with current card and cc fields', function () {
|
|
stubMissingRoutes();
|
|
$abo = makePortalAboFixture()['abo'];
|
|
|
|
$html = view('portal.abo._payment_method_form', ['user_abo' => $abo])->render();
|
|
|
|
expect($html)
|
|
->toContain('name="cc_cardpan"')
|
|
->toContain('name="cc_cardcvc2"')
|
|
->toContain('name="cc_cardexpiremonth"')
|
|
->toContain('411111XXXXXX0000 (01/25)')
|
|
->toContain(__('abo.update_payment_method'))
|
|
->toContain(__('abo.card_security_notice'));
|
|
});
|
|
|
|
it('applies the stashed card on a successful 3-D Secure return', function () {
|
|
stubMissingRoutes();
|
|
$fixture = makePortalAboFixture();
|
|
$this->actingAs($fixture['customer'], 'customers');
|
|
|
|
session(['abo_card_update.'.$fixture['abo']->id => [
|
|
'carddata' => ['pseudocardpan' => 'NEW_PSEUDO', 'truncatedcardpan' => '555555XXXXXX9999', 'cardexpiredate' => '3011'],
|
|
'userid' => 777,
|
|
]]);
|
|
|
|
(new AboController)->paymentMethodReturn(
|
|
$fixture['abo']->id, 'success', app(AboPaymentMethodService::class)
|
|
);
|
|
|
|
$abo = $fixture['abo']->fresh();
|
|
expect($abo->carddata['pseudocardpan'])->toBe('NEW_PSEUDO')
|
|
->and($abo->payone_userid)->toEqual(777)
|
|
->and($abo->card_update_pending_at)->toBeNull()
|
|
->and($abo->card_changes()->count())->toBe(1)
|
|
->and($abo->card_changes()->first()->source)->toBe('portal')
|
|
->and($abo->card_changes()->first()->masked_card)->toBe('555555XXXXXX9999 (11/30)');
|
|
});
|
|
|
|
it('clears the pending trace and keeps the card on a cancelled 3-D Secure return', function () {
|
|
stubMissingRoutes();
|
|
$fixture = makePortalAboFixture();
|
|
$this->actingAs($fixture['customer'], 'customers');
|
|
|
|
(new AboController)->paymentMethodReturn(
|
|
$fixture['abo']->id, 'cancel', app(AboPaymentMethodService::class)
|
|
);
|
|
|
|
$abo = $fixture['abo']->fresh();
|
|
expect($abo->carddata['pseudocardpan'])->toBe('OLD_PSEUDO')
|
|
->and($abo->payone_userid)->toEqual(111)
|
|
->and($abo->card_update_pending_at)->toBeNull();
|
|
});
|