Files
clinicpro/tests/PaymentMethod/PaymentMethodTest.php
T
hamedandClaude Opus 4.8 b459d082a4 feat: port payment management tab from tauri to admin dashboard
Add per-clinic payment methods (bank accounts + POS/card-reader devices)
under the "مدیریت پرداخت" settings tab at /admin/my-financial, ported from
clinic-pro-tauri's mock-only PaymentManagement tab into a real persisted
feature. These records are referenceable (by uuid) from patient invoices to
record which method a service payment was made with.

Backend (new src/PaymentMethod domain):
- BankAccount + Pos entities, repositories, PaymentMethodService (validation,
  ownership scoping, create/update/toggle logic).
- Thin PaymentMethodController exposing /api/v1/my/payment-methods/{bank-accounts,pos}
  (GET/POST/PUT + PATCH .../status), guarded to clinic/doctor/secretary/admin.
- Migration for bank_accounts + pos_devices tables.
- Functional tests (success + validation/404/403 + empty boundaries).
- docs/api/payment-method.md.

Frontend:
- Replace MyFinancialPage content with the payment-management UI (two tabs,
  tables, add/edit modals, status toggle) using the admin design system.
- usePaymentMethods hook (TanStack Query) + presentational components.
- Update page test to cover tabs, data, empty state and the add modal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:39:32 +03:30

192 lines
6.8 KiB
PHP

<?php
namespace App\Tests\PaymentMethod;
use App\Tests\ApiTestCase;
/**
* Functional coverage for the per-clinic payment methods API
* (bank accounts + POS devices). Success, error and boundary cases.
*/
class PaymentMethodTest extends ApiTestCase
{
// ---- Bank accounts -----------------------------------------------------
public function testEmptyBankAccountListForNewClinic(): void
{
$user = $this->createUser(['ROLE_CLINIC']);
$res = $this->authJson('GET', '/api/v1/my/payment-methods/bank-accounts', $user);
$this->assertSame(200, $this->responseCode());
$this->assertTrue($res['success']);
$this->assertSame([], $res['data']);
}
public function testCreateAndListBankAccount(): void
{
$user = $this->createUser(['ROLE_CLINIC']);
$created = $this->authJson('POST', '/api/v1/my/payment-methods/bank-accounts', $user, [
'bank_name' => 'ملی',
'card_number' => '6037991234567890',
'account_number' => '0101234567890',
'shaba_number' => 'IR820540102680020817909002',
]);
$this->assertSame(201, $this->responseCode());
$this->assertSame('ملی', $created['data']['bank_name']);
$this->assertTrue($created['data']['is_active']);
$this->assertNotEmpty($created['data']['uuid']);
$list = $this->authJson('GET', '/api/v1/my/payment-methods/bank-accounts', $user);
$this->assertCount(1, $list['data']);
$this->assertSame('0101234567890', $list['data'][0]['account_number']);
}
public function testCreateBankAccountValidationErrorWhenBankNameMissing(): void
{
$user = $this->createUser(['ROLE_CLINIC']);
$res = $this->authJson('POST', '/api/v1/my/payment-methods/bank-accounts', $user, [
'account_number' => '0101234567890',
]);
$this->assertSame(422, $this->responseCode());
$this->assertFalse($res['success']);
$this->assertSame('bank_name', $res['errors'][0]['field']);
}
public function testUpdateBankAccount(): void
{
$user = $this->createUser(['ROLE_CLINIC']);
$created = $this->authJson('POST', '/api/v1/my/payment-methods/bank-accounts', $user, [
'bank_name' => 'ملی',
'account_number' => '0101234567890',
]);
$uuid = $created['data']['uuid'];
$updated = $this->authJson('PUT', "/api/v1/my/payment-methods/bank-accounts/$uuid", $user, [
'bank_name' => 'ملت',
'account_number' => '0209876543210',
]);
$this->assertSame(200, $this->responseCode());
$this->assertSame('ملت', $updated['data']['bank_name']);
$this->assertSame('0209876543210', $updated['data']['account_number']);
}
public function testToggleBankAccountStatus(): void
{
$user = $this->createUser(['ROLE_CLINIC']);
$created = $this->authJson('POST', '/api/v1/my/payment-methods/bank-accounts', $user, [
'bank_name' => 'ملی',
'account_number' => '0101234567890',
]);
$uuid = $created['data']['uuid'];
$this->assertTrue($created['data']['is_active']);
$toggled = $this->authJson('PATCH', "/api/v1/my/payment-methods/bank-accounts/$uuid/status", $user);
$this->assertSame(200, $this->responseCode());
$this->assertFalse($toggled['data']['is_active']);
}
public function testToggleUnknownBankAccountReturns404(): void
{
$user = $this->createUser(['ROLE_CLINIC']);
$res = $this->authJson('PATCH', '/api/v1/my/payment-methods/bank-accounts/does-not-exist/status', $user);
$this->assertSame(404, $this->responseCode());
$this->assertFalse($res['success']);
}
public function testCannotTouchAnotherClinicsBankAccount(): void
{
$owner = $this->createUser(['ROLE_CLINIC']);
$other = $this->createUser(['ROLE_CLINIC']);
$created = $this->authJson('POST', '/api/v1/my/payment-methods/bank-accounts', $owner, [
'bank_name' => 'ملی',
'account_number' => '0101234567890',
]);
$uuid = $created['data']['uuid'];
$res = $this->authJson('PATCH', "/api/v1/my/payment-methods/bank-accounts/$uuid/status", $other);
$this->assertSame(404, $this->responseCode());
$this->assertFalse($res['success']);
}
public function testBankAccountForbiddenForPlainUser(): void
{
$user = $this->createUser(['ROLE_USER']);
$this->authJson('GET', '/api/v1/my/payment-methods/bank-accounts', $user);
$this->assertSame(403, $this->responseCode());
}
// ---- POS devices -------------------------------------------------------
public function testCreateAndListPos(): void
{
$user = $this->createUser(['ROLE_DOCTOR']);
$created = $this->authJson('POST', '/api/v1/my/payment-methods/pos', $user, [
'bank_name' => 'ملت',
'serial_number' => 'SN-98765',
'terminal_number' => '123456',
]);
$this->assertSame(201, $this->responseCode());
$this->assertSame('ملت', $created['data']['bank_name']);
$this->assertSame('123456', $created['data']['terminal_number']);
$this->assertTrue($created['data']['is_active']);
$list = $this->authJson('GET', '/api/v1/my/payment-methods/pos', $user);
$this->assertCount(1, $list['data']);
}
public function testCreatePosValidationErrorWhenTerminalMissing(): void
{
$user = $this->createUser(['ROLE_DOCTOR']);
$res = $this->authJson('POST', '/api/v1/my/payment-methods/pos', $user, [
'bank_name' => 'ملت',
]);
$this->assertSame(422, $this->responseCode());
$this->assertSame('terminal_number', $res['errors'][0]['field']);
}
public function testTogglePosStatus(): void
{
$user = $this->createUser(['ROLE_SECRETARY']);
$created = $this->authJson('POST', '/api/v1/my/payment-methods/pos', $user, [
'bank_name' => 'تجارت',
'terminal_number' => '345678',
]);
$uuid = $created['data']['uuid'];
$toggled = $this->authJson('PATCH', "/api/v1/my/payment-methods/pos/$uuid/status", $user);
$this->assertSame(200, $this->responseCode());
$this->assertFalse($toggled['data']['is_active']);
}
public function testPosListIsolatedPerUser(): void
{
$a = $this->createUser(['ROLE_CLINIC']);
$b = $this->createUser(['ROLE_CLINIC']);
$this->authJson('POST', '/api/v1/my/payment-methods/pos', $a, [
'bank_name' => 'صادرات',
'terminal_number' => '901234',
]);
$listB = $this->authJson('GET', '/api/v1/my/payment-methods/pos', $b);
$this->assertSame([], $listB['data']);
}
}