feat: enhance staff management and payment gateway features
- Fix national code handling in staff creation and updates to support Persian digits. - Update ClinicStaff entity to allow longer national codes (up to 15 characters). - Implement support for clinic secretaries in SecretaryController, allowing creation without a doctor UUID. - Add a new endpoint to retrieve doctors associated with a clinic for secretary management. - Improve appointment management by ensuring doctors are selectable even when no appointments exist. - Extend PatientController to allow secretaries to create patient records if they have the appropriate permissions. - Introduce a PriceInput component for better price formatting in forms, supporting Persian digits. - Add a MockGateway for testing payment processes without real transactions. - Enhance SMS settings management with an approval flow for post-visit text messages, including new fields for pending text and status. - Update migrations to reflect changes in database schema for national codes and SMS settings.
This commit is contained in:
@@ -8,7 +8,9 @@ use App\Auth\Entity\User;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
use App\Doctor\Repository\DoctorRepository;
|
||||
use App\Payment\Entity\Payment;
|
||||
use App\Config\Repository\SiteConfigRepository;
|
||||
use App\Payment\Gateway\MellatGateway;
|
||||
use App\Payment\Gateway\MockGateway;
|
||||
use App\Payment\Gateway\SepGateway;
|
||||
use App\Payment\Repository\PaymentRepository;
|
||||
use App\Payment\Service\CircuitBreakerService;
|
||||
@@ -38,11 +40,13 @@ class PaymentController extends BaseController
|
||||
private readonly AppointmentRepository $appointmentRepo,
|
||||
private readonly MellatGateway $mellat,
|
||||
private readonly SepGateway $sep,
|
||||
private readonly MockGateway $mock,
|
||||
private readonly CircuitBreakerService $circuitBreaker,
|
||||
private readonly SubscriptionService $subscriptionService,
|
||||
private readonly SmsWalletService $smsWalletService,
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly SiteConfigRepository $configRepo,
|
||||
private readonly string $appBaseUrl,
|
||||
private readonly string $allowedFrontendHosts = '',
|
||||
) {}
|
||||
@@ -520,6 +524,10 @@ class PaymentController extends BaseController
|
||||
|
||||
private function resolveGateway(string $name): \App\Payment\Gateway\PaymentGatewayInterface|null
|
||||
{
|
||||
if ($this->configRepo->get('payment_test_mode') === '1') {
|
||||
return $this->mock;
|
||||
}
|
||||
|
||||
return match ($name) {
|
||||
'mellat' => $this->mellat,
|
||||
'sep' => $this->sep,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
use App\Config\Repository\SiteConfigRepository;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class MellatGateway implements PaymentGatewayInterface
|
||||
@@ -9,14 +10,20 @@ class MellatGateway implements PaymentGatewayInterface
|
||||
private const PAYMENT_URL = 'https://bpm.shaparak.ir/pgwchannel/startpay.mellat';
|
||||
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly string $terminalId,
|
||||
private readonly string $username,
|
||||
private readonly string $password,
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly SiteConfigRepository $configRepo,
|
||||
private readonly string $terminalId = '',
|
||||
private readonly string $username = '',
|
||||
private readonly string $password = '',
|
||||
) {}
|
||||
|
||||
public function getName(): string { return 'mellat'; }
|
||||
|
||||
private function cfg(string $key, string $envFallback): string
|
||||
{
|
||||
return $this->configRepo->get($key) ?: $envFallback;
|
||||
}
|
||||
|
||||
public function initiate(int $amountRials, string $orderId, string $callbackUrl): PaymentInitResult
|
||||
{
|
||||
try {
|
||||
@@ -74,13 +81,17 @@ class MellatGateway implements PaymentGatewayInterface
|
||||
|
||||
private function buildRequestPayload(int $amount, string $orderId, string $callbackUrl): string
|
||||
{
|
||||
$terminalId = $this->cfg('mellat_terminal_id', $this->terminalId);
|
||||
$username = $this->cfg('mellat_username', $this->username);
|
||||
$password = $this->cfg('mellat_password', $this->password);
|
||||
|
||||
return <<<XML
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:int="http://interfaces.core.sw.bsl.com/westernmellat">
|
||||
<soapenv:Body>
|
||||
<int:bpPayRequest>
|
||||
<terminalId>{$this->terminalId}</terminalId>
|
||||
<userName>{$this->username}</userName>
|
||||
<userPassword>{$this->password}</userPassword>
|
||||
<terminalId>{$terminalId}</terminalId>
|
||||
<userName>{$username}</userName>
|
||||
<userPassword>{$password}</userPassword>
|
||||
<orderId>{$orderId}</orderId>
|
||||
<amount>{$amount}</amount>
|
||||
<localDate>{$this->date()}</localDate>
|
||||
@@ -96,13 +107,17 @@ XML;
|
||||
|
||||
private function buildVerifyPayload(string $refId): string
|
||||
{
|
||||
$terminalId = $this->cfg('mellat_terminal_id', $this->terminalId);
|
||||
$username = $this->cfg('mellat_username', $this->username);
|
||||
$password = $this->cfg('mellat_password', $this->password);
|
||||
|
||||
return <<<XML
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:int="http://interfaces.core.sw.bsl.com/westernmellat">
|
||||
<soapenv:Body>
|
||||
<int:bpVerifyRequest>
|
||||
<terminalId>{$this->terminalId}</terminalId>
|
||||
<userName>{$this->username}</userName>
|
||||
<userPassword>{$this->password}</userPassword>
|
||||
<terminalId>{$terminalId}</terminalId>
|
||||
<userName>{$username}</userName>
|
||||
<userPassword>{$password}</userPassword>
|
||||
<orderId>{$refId}</orderId>
|
||||
<saleOrderId>{$refId}</saleOrderId>
|
||||
<saleReferenceId>{$refId}</saleReferenceId>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
class MockGateway implements PaymentGatewayInterface
|
||||
{
|
||||
public function getName(): string { return 'mock'; }
|
||||
|
||||
public function initiate(int $amountRials, string $orderId, string $callbackUrl): PaymentInitResult
|
||||
{
|
||||
$redirectUrl = $callbackUrl . '&mock=1&ResCode=0&RefId=MOCK-' . $orderId;
|
||||
return new PaymentInitResult(true, redirectUrl: $redirectUrl, token: 'MOCK-' . $orderId);
|
||||
}
|
||||
|
||||
public function verify(array $callbackData): PaymentVerifyResult
|
||||
{
|
||||
$mock = $callbackData['mock'] ?? '0';
|
||||
$resCode = $callbackData['ResCode'] ?? ($callbackData['State'] ?? '');
|
||||
|
||||
if ($mock !== '1' && $mock !== 1) {
|
||||
return new PaymentVerifyResult(false, errorMessage: 'mock callback مجاز نیست');
|
||||
}
|
||||
|
||||
$refId = $callbackData['RefId'] ?? $callbackData['order_id'] ?? 'MOCK-REF';
|
||||
return new PaymentVerifyResult(true, referenceId: $refId);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
use App\Config\Repository\SiteConfigRepository;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class SepGateway implements PaymentGatewayInterface
|
||||
@@ -10,19 +11,25 @@ class SepGateway implements PaymentGatewayInterface
|
||||
private const PAYMENT_URL = 'https://sep.shaparak.ir/OnlinePG/OnlinePG';
|
||||
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly string $terminalId,
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly SiteConfigRepository $configRepo,
|
||||
private readonly string $terminalId = '',
|
||||
) {}
|
||||
|
||||
public function getName(): string { return 'sep'; }
|
||||
|
||||
private function cfg(string $key, string $envFallback): string
|
||||
{
|
||||
return $this->configRepo->get($key) ?: $envFallback;
|
||||
}
|
||||
|
||||
public function initiate(int $amountRials, string $orderId, string $callbackUrl): PaymentInitResult
|
||||
{
|
||||
try {
|
||||
$response = $this->httpClient->request('POST', self::TOKEN_URL, [
|
||||
'json' => [
|
||||
'action' => 'token',
|
||||
'TerminalId' => $this->terminalId,
|
||||
'TerminalId' => $this->cfg('sep_terminal_id', $this->terminalId),
|
||||
'Amount' => $amountRials,
|
||||
'ResNum' => $orderId,
|
||||
'RedirectUrl' => $callbackUrl,
|
||||
@@ -57,7 +64,7 @@ class SepGateway implements PaymentGatewayInterface
|
||||
$response = $this->httpClient->request('POST', self::TOKEN_URL, [
|
||||
'json' => [
|
||||
'action' => 'verify',
|
||||
'TerminalId' => $this->terminalId,
|
||||
'TerminalId' => $this->cfg('sep_terminal_id', $this->terminalId),
|
||||
'RefNum' => $refNum,
|
||||
],
|
||||
'timeout' => 10,
|
||||
|
||||
Reference in New Issue
Block a user