feat(payment): implement expiration handling for appointment payments
This commit is contained in:
@@ -170,6 +170,15 @@ class Appointment
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** آیا پنجرهٔ ۱۵ دقیقهایِ پرداخت گذشته یا زمان اسلات رد شده است؟ */
|
||||
public function isPaymentWindowExpired(int $now): bool
|
||||
{
|
||||
if ($this->expiresAt !== null && $now > $this->expiresAt) {
|
||||
return true;
|
||||
}
|
||||
return $now >= $this->slotStart;
|
||||
}
|
||||
|
||||
public function canTransitionTo(string $newStatus): bool
|
||||
{
|
||||
return in_array($newStatus, self::ALLOWED_TRANSITIONS[$this->status] ?? [], true);
|
||||
|
||||
@@ -155,6 +155,14 @@ class PaymentController extends BaseController
|
||||
return $this->redirectToReturn($return, 'notfound');
|
||||
}
|
||||
|
||||
// نوبتِ منقضی (status=expired یا پنجرهٔ ۱۵ دقیقهای گذشته) → غیرقابلپرداخت.
|
||||
if ($appointment->getStatus() === Appointment::STATUS_EXPIRED
|
||||
|| ($appointment->getStatus() === Appointment::STATUS_PENDING
|
||||
&& $appointment->isPaymentWindowExpired(time()))) {
|
||||
$this->expireAppointmentPayment($appointment);
|
||||
return $this->redirectToReturn($return, 'expired');
|
||||
}
|
||||
|
||||
// اعتبارسنجی سفارش: فقط نوبت قابلپرداخت.
|
||||
if (!in_array($appointment->getStatus(), [Appointment::STATUS_PENDING, Appointment::STATUS_CONFIRMED], true)) {
|
||||
return $this->redirectToReturn($return, 'invalid');
|
||||
@@ -194,6 +202,21 @@ class PaymentController extends BaseController
|
||||
return $payment;
|
||||
}
|
||||
|
||||
/** نوبتِ منقضی را expired و پرداخت pending آن را canceled میکند (idempotent). */
|
||||
private function expireAppointmentPayment(Appointment $appointment): void
|
||||
{
|
||||
if ($appointment->getStatus() === Appointment::STATUS_PENDING
|
||||
&& $appointment->canTransitionTo(Appointment::STATUS_EXPIRED)) {
|
||||
$appointment->transitionTo(Appointment::STATUS_EXPIRED);
|
||||
$this->appointmentRepo->save($appointment);
|
||||
}
|
||||
$payment = $this->paymentRepo->findPendingByAppointment($appointment);
|
||||
if ($payment !== null) {
|
||||
$payment->setStatus(Payment::STATUS_CANCELED);
|
||||
$this->paymentRepo->save($payment);
|
||||
}
|
||||
}
|
||||
|
||||
private function redirectToReturn(string $return, string $status): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
if ($return !== '' && $this->isAllowedFrontend($return)) {
|
||||
@@ -224,6 +247,16 @@ class PaymentController extends BaseController
|
||||
return $this->renderPaymentResult('notfound');
|
||||
}
|
||||
|
||||
// پرداختِ نوبت: اگر نوبت منقضی شده باشد، پرداخت هم منقضی و غیرقابلپرداخت است.
|
||||
$appointment = $payment->getAppointment();
|
||||
if ($payment->getType() === Payment::TYPE_APPOINTMENT && $appointment !== null
|
||||
&& ($appointment->getStatus() === Appointment::STATUS_EXPIRED
|
||||
|| ($appointment->getStatus() === Appointment::STATUS_PENDING
|
||||
&& $appointment->isPaymentWindowExpired(time())))) {
|
||||
$this->expireAppointmentPayment($appointment);
|
||||
return $this->renderPaymentResult('expired', $payment);
|
||||
}
|
||||
|
||||
// فقط پرداخت در انتظار قابل انتقال به درگاه است (جلوگیری از پرداخت تکراری/replay).
|
||||
if ($payment->getStatus() !== Payment::STATUS_PENDING) {
|
||||
return $this->redirectToFrontend($payment, $payment->getStatus() === Payment::STATUS_SUCCESS);
|
||||
@@ -558,6 +591,7 @@ class PaymentController extends BaseController
|
||||
'gateway' => ['درگاه نامعتبر', 'درگاه پرداخت انتخابی نامعتبر یا غیرفعال است.'],
|
||||
'invalid_return' => ['آدرس بازگشت نامعتبر', 'آدرس بازگشت مجاز نیست.'],
|
||||
'forbidden' => ['دسترسی غیرمجاز', 'این درخواست از مبدأ مجاز ارسال نشده است.'],
|
||||
'expired' => ['مهلت پرداخت به پایان رسید', 'مهلت ۱۵ دقیقهایِ پرداخت این نوبت تمام شده است. لطفاً دوباره نوبت بگیرید.'],
|
||||
];
|
||||
[$title, $message] = $labels[$status] ?? ['خطا در پرداخت', 'خطایی در فرآیند پرداخت رخ داد.'];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user