feat(admin): show which domain a payment came from

Several storefronts share one gateway — the city booking sites and the
ClinicPro panel itself — so the payments report could not say where a
transaction originated. The payment already stores the return address it was
started with; the domain is derived from it.

Lowercased and stripped of "www." so one domain is one value in the report,
null when a payment has no return address. Exposed on both the list and the
detail (which also carries the full address), and the list search now matches
it, so filtering to a single site needs no new parameter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-19 22:38:27 +03:30
co-authored by Claude Opus 5
parent 60c2eaa35e
commit bb09316a75
7 changed files with 173 additions and 5 deletions
+28 -3
View File
@@ -1063,7 +1063,7 @@ class AdminApiController extends BaseController
$qb = $this->em->createQueryBuilder()
->select(
'p.uuid, p.amountRials, p.status, p.gateway, p.referenceId, p.createdAt, p.updatedAt',
'p.uuid, p.amountRials, p.status, p.gateway, p.referenceId, p.frontendAddress, p.createdAt, p.updatedAt',
'u.mobileNumber as patient_mobile',
)
->from(Payment::class, 'p')
@@ -1071,7 +1071,7 @@ class AdminApiController extends BaseController
->orderBy('p.createdAt', 'DESC');
if ($search !== '') {
$qb->andWhere('u.mobileNumber LIKE :s OR p.referenceId LIKE :s OR p.orderId LIKE :s')
$qb->andWhere('u.mobileNumber LIKE :s OR p.referenceId LIKE :s OR p.orderId LIKE :s OR p.frontendAddress LIKE :s')
->setParameter('s', '%' . $search . '%');
}
if ($status !== '') {
@@ -1089,6 +1089,7 @@ class AdminApiController extends BaseController
'status' => $p['status'],
'gateway' => $p['gateway'],
'ref_id' => $p['referenceId'],
'origin' => $this->paymentOrigin($p['frontendAddress']),
'patient_mobile' => $p['patient_mobile'],
'paid_at' => $p['status'] === 'success' ? date('c', (int) $p['updatedAt']) : null,
'created_at' => date('c', (int) $p['createdAt']),
@@ -1114,7 +1115,7 @@ class AdminApiController extends BaseController
{
$rows = $this->em->createQueryBuilder()
->select(
'p.uuid, p.orderId, p.amountRials, p.status, p.gateway, p.type, p.referenceId, p.metadata, p.createdAt, p.updatedAt',
'p.uuid, p.orderId, p.amountRials, p.status, p.gateway, p.type, p.referenceId, p.metadata, p.frontendAddress, p.createdAt, p.updatedAt',
'u.mobileNumber as patient_mobile, u.realName as patient_name',
'a.uuid as appointment_uuid',
)
@@ -1137,6 +1138,8 @@ class AdminApiController extends BaseController
'gateway' => $p['gateway'],
'type' => $p['type'],
'ref_id' => $p['referenceId'],
'origin' => $this->paymentOrigin($p['frontendAddress']),
'frontend_address' => $p['frontendAddress'],
'card_pan' => $p['metadata']['card_pan'] ?? null,
'refunds' => $p['metadata']['refunds'] ?? [],
'patient_mobile' => $p['patient_mobile'],
@@ -1147,6 +1150,28 @@ class AdminApiController extends BaseController
]);
}
/**
* دامنهٔ مبدأ پرداخت، از آدرس بازگشتِ ثبت‌شده روی خودِ پرداخت.
*
* محصول چند مبدأ دارد (سایت‌های نوبت‌دهی شهری، پنل خودِ کلینیک‌پرو) و همه به یک
* درگاه می‌روند؛ بدون این، گزارش پرداخت‌ها نمی‌گوید تراکنش از کدام سایت آمده.
*
* `www.` حذف می‌شود تا دو نوشتنِ یک دامنه در گزارش دو سطر جدا نشوند.
*/
private function paymentOrigin(?string $frontendAddress): ?string
{
if ($frontendAddress === null || trim($frontendAddress) === '') {
return null;
}
$host = parse_url(trim($frontendAddress), PHP_URL_HOST);
if (!is_string($host) || $host === '') {
return null;
}
return preg_replace('/^www\./i', '', strtolower($host));
}
#[Route('/api/v1/admin/payments/{uuid}/refund', methods: ['POST'])]
public function refundPayment(string $uuid, Request $request): JsonResponse
{