Files
clinicpro/.claude/prompt/my-payments-list-endpoint.md
hamedandClaude Opus 4.8 038cb73ea7 feat(payment): add GET /api/v1/my/payments for the logged-in user
Add PaymentRepository::findByUser/countByUser and a paginated
my/payments endpoint that returns the authenticated user's own payments
(derived from the token, never a userId in the URL). Public dashboard's
transactions tab can now list payments instead of hitting a nonexistent
route.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 19:21:31 +03:30

6.8 KiB
Raw Permalink Blame History

افزودن endpoint لیست پرداخت‌های کاربر لاگین‌شده (GET /api/v1/my/payments)

پروژه

clinicpro (Backend). این پرامپت اول اجرا شود.

Cross-repo: سایت عمومی (nobat724_front) این endpoint را در تب «تراکنش‌ها»ی داشبورد مصرف می‌کند. پرامپت همتای frontend: nobat724_front/.claude/prompt/fix-dashboard-uuid-and-lists.md

زمینه

داشبورد کاربر در سایت عمومی تب «تراکنش‌ها» دارد که request.getMyPayments(userId) را صدا می‌زند → GET /api/v1/payment/my-payments/{userId}. اما چنین route‌ای در backend وجود ندارد (تأییدشده با debug:router): تنها endpointهای پرداخت، GET /api/v1/payment/{uuid} (تک پرداخت) و GET /api/v1/admin/payments (ادمین) هستند. در نتیجه تب تراکنش‌ها همیشه 404 می‌گیرد.

Payment به user (پرداخت‌کننده) لینک دارد و toArray() دارد، ولی PaymentRepository متد findByUser ندارد.

مشکل / هدف

یک endpoint عمومی-برای-کاربر بساز: GET /api/v1/my/payments که پرداخت‌های خودِ کاربر لاگین‌شده را (از روی توکن، نه از روی userId در URL) به‌صورت لیست برمی‌گرداند. الگو دقیقاً مثل MyAppointmentsController و قرارداد پاسخ مثل بقیه‌ی listهای کاربر.

فایل‌های مرتبط

فایل نقش
src/Payment/Controller/PaymentController.php افزودن متد myPayments() (یا کنترلر MyPaymentsController جدید)
src/Payment/Repository/PaymentRepository.php افزودن findByUser(User $user, ?string $status, int $page, int $limit)
src/Payment/Entity/Payment.php getUser(), toArray() موجود
src/Appointment/Controller/MyAppointmentsController.php مرجع الگو (CurrentUser, paginated)
docs/api/payment.md مستندسازی endpoint جدید

وضعیت فعلی (کد واقعی)

PaymentRepository — بدون findByUser

public function findByUuid(string $uuid): ?Payment { ... }
public function findByOrderId(string $orderId): ?Payment { ... }
public function save(Payment $entity, bool $flush = true): void { ... }

Payment — لینک user + toArray

#[ORM\JoinColumn(name: 'user_id', ...)] private User $user;
public function getUser(): User { return $this->user; }
public function getOrderId(): string { ... }
public function getAmountRials(): int { ... }
public function getStatus(): string { ... }
public function toArray(): array { ... }   // شکل پرداخت

route ناموجود که فرانت صدا می‌زند

GET /api/v1/payment/my-payments/{userId}   → 404 (وجود ندارد)

وظایف

۱. findByUser در PaymentRepository

/** @return Payment[] */
public function findByUser(User $user, ?string $status = null, int $page = 1, int $limit = 20): array
{
    $qb = $this->createQueryBuilder('p')
        ->where('p.user = :user')->setParameter('user', $user)
        ->orderBy('p.createdAt', 'DESC')
        ->setFirstResult(($page - 1) * $limit)
        ->setMaxResults($limit);
    if ($status !== null) {
        $qb->andWhere('p.status = :status')->setParameter('status', $status);
    }
    return $qb->getQuery()->getResult();
}

public function countByUser(User $user, ?string $status = null): int
{
    $qb = $this->createQueryBuilder('p')->select('COUNT(p.id)')
        ->where('p.user = :user')->setParameter('user', $user);
    if ($status !== null) {
        $qb->andWhere('p.status = :status')->setParameter('status', $status);
    }
    return (int) $qb->getQuery()->getSingleScalarResult();
}

از User به‌جای user_id در DQL استفاده کن (App\Auth\Entity\User را import کن). status اختیاری (pending/paid/failed/...).

۲. endpoint GET /api/v1/my/payments

در PaymentController (یا کنترلر جدید MyPaymentsController هم‌سبک با MyAppointmentsController):

#[Route('/api/v1/my/payments', methods: ['GET'])]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function myPayments(Request $request, #[CurrentUser] User $user): JsonResponse
{
    $page   = max(1, (int) $request->query->get('page', 1));
    $limit  = min(100, max(1, (int) $request->query->get('limit', 20)));
    $status = $request->query->get('status');

    $items = array_map(
        fn(Payment $p) => $p->toArray(),
        $this->paymentRepo->findByUser($user, $status, $page, $limit)
    );
    $total = $this->paymentRepo->countByUser($user, $status);

    return $this->paginated($items, $total, $page, $limit);
}

از paginated() استفاده کن (نه success تودرتو) تا فرانت items را از data و total را از meta.totalRecords بخواند — هم‌خوان با بقیه‌ی listها. کاربر فقط پرداخت‌های خودش را می‌بیند (از #[CurrentUser]؛ هیچ userId از URL گرفته نمی‌شود).

۳. مطمئن شو route عمومی/auth درست است

  • /api/v1/my/payments زیر فایروال ^/api با IS_AUTHENTICATED_FULLY می‌افتد (نیاز به whitelist عمومی ندارد چون کاربر لاگین است). نیازی به تغییر security.yaml نیست. تأیید کن با debug:router.

۴. مستندسازی

در docs/api/payment.md: endpoint GET /api/v1/my/payments با permission، query params (page, limit, status)، و نمونه‌ی پاسخ paginated واقعی (items + meta).

نکات مهم

  • هیچ migration لازم نیست (فقط query + route).
  • امنیت: هرگز userId از URL نگیر؛ فقط #[CurrentUser]. کاربر نباید پرداخت دیگران را ببیند.
  • پاسخ‌ها از BaseController؛ این endpoint باید paginated() بدهد (نه success(['data'=>...])) — فرانت data (آرایه) و meta.totalRecords می‌خواند.
  • toArray() پرداخت شکل واقعی را می‌دهد؛ اگر فیلدی برای نمایش لازم است (مبلغ، وضعیت، تاریخ، نوع، order_id، appointment_uuid) و در toArray نیست، اضافه کن.
  • تست:
    • ddev exec php -l روی فایل‌های تغییر یافته
    • ddev exec php bin/console cache:clear
    • ddev exec php bin/console debug:router | grep "my/payments"
    • با توکن یک کاربر (مثلاً 09210651788 که یک پرداخت آزمایشی دارد): GET /api/v1/my/payments → ۲۰۰ با لیست پرداخت‌های همان کاربر؛ بدون توکن → ۴۰۱.
    • docs/api/payment.md به‌روز شود.