feat(payment): add refund and reversal functionality to payment gateways
- Implemented `refund` and `reverse` methods in `PaymentGatewayInterface`. - Added `PaymentRefundResult` class to handle refund operation results. - Enhanced `MockGateway` and `SepGateway` to support refund and reversal operations. - Updated `PaymentManager` to include `refundPayment` and `reversePayment` methods for handling refunds and reversals in transactions. - Modified `ClinicSubscriptionRepository` and `SubscriptionService` to manage subscriptions during refunds. - Added admin API endpoints for processing refunds and reversals. - Updated security headers to allow form actions to the sandbox environment. - Documented the new refund and reversal features in the API documentation.
This commit is contained in:
@@ -37,6 +37,8 @@ class AdminApiController extends BaseController
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly \App\Appointment\Service\SlotCalculatorService $slotCalculator,
|
||||
private readonly \App\Insurance\Service\TenantInsuranceCleanupService $insuranceCleanup,
|
||||
private readonly \App\Payment\Service\PaymentManager $paymentManager,
|
||||
private readonly \App\Payment\Repository\PaymentRepository $paymentRepo,
|
||||
) {}
|
||||
|
||||
// ── Users ─────────────────────────────────────────────────────────────────
|
||||
@@ -891,7 +893,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.createdAt, p.updatedAt',
|
||||
'p.uuid, p.orderId, p.amountRials, p.status, p.gateway, p.type, p.referenceId, p.metadata, p.createdAt, p.updatedAt',
|
||||
'u.mobileNumber as patient_mobile, u.realName as patient_name',
|
||||
'a.uuid as appointment_uuid',
|
||||
)
|
||||
@@ -914,6 +916,8 @@ class AdminApiController extends BaseController
|
||||
'gateway' => $p['gateway'],
|
||||
'type' => $p['type'],
|
||||
'ref_id' => $p['referenceId'],
|
||||
'card_pan' => $p['metadata']['card_pan'] ?? null,
|
||||
'refunds' => $p['metadata']['refunds'] ?? [],
|
||||
'patient_mobile' => $p['patient_mobile'],
|
||||
'patient_name' => $p['patient_name'],
|
||||
'appointment_uuid' => $p['appointment_uuid'],
|
||||
@@ -922,6 +926,39 @@ class AdminApiController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/admin/payments/{uuid}/refund', methods: ['POST'])]
|
||||
public function refundPayment(string $uuid, Request $request): JsonResponse
|
||||
{
|
||||
$payment = $this->paymentRepo->findOneBy(['uuid' => $uuid]);
|
||||
if ($payment === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پرداخت یافت نشد', 404);
|
||||
}
|
||||
$amount = $request->toArray()['amount'] ?? null; // ریال؛ null = کل مبلغ
|
||||
$result = $this->paymentManager->refundPayment(
|
||||
$payment,
|
||||
$amount !== null ? (int) $amount : null,
|
||||
$request->getClientIp() ?? '',
|
||||
);
|
||||
|
||||
return $result->success
|
||||
? $this->success(['status' => $payment->getStatus(), 'refund_ref' => $result->refundRefId])
|
||||
: $this->error(ErrorCodes::ERR_PAYMENT_002, $result->errorMessage, 422);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/admin/payments/{uuid}/reverse', methods: ['POST'])]
|
||||
public function reversePayment(string $uuid, Request $request): JsonResponse
|
||||
{
|
||||
$payment = $this->paymentRepo->findOneBy(['uuid' => $uuid]);
|
||||
if ($payment === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پرداخت یافت نشد', 404);
|
||||
}
|
||||
$result = $this->paymentManager->reversePayment($payment, $request->getClientIp() ?? '');
|
||||
|
||||
return $result->success
|
||||
? $this->success(['status' => $payment->getStatus()])
|
||||
: $this->error(ErrorCodes::ERR_PAYMENT_002, $result->errorMessage, 422);
|
||||
}
|
||||
|
||||
// ── Representations ───────────────────────────────────────────────────────
|
||||
|
||||
#[OA\Get(
|
||||
|
||||
Reference in New Issue
Block a user