feat: Implement SMS sending functionality with KavehNegar and Rangineh providers
- Add SendSmsMessage class for encapsulating SMS message data. - Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS. - Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates. - Develop SendSmsHandler for handling SMS sending messages. - Create SmsService to manage SMS dispatching and logging. - Add UserProfileController for managing user profiles with CRUD operations. - Implement UserProfile entity and repository for user profile data management. - Update symfony.lock and bootstrap.php for project dependencies and environment setup.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
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,
|
||||
) {}
|
||||
|
||||
public function getName(): string { return 'mellat'; }
|
||||
|
||||
public function initiate(int $amountRials, string $orderId, string $callbackUrl): PaymentInitResult
|
||||
{
|
||||
try {
|
||||
$response = $this->httpClient->request('POST',
|
||||
'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl', [
|
||||
'body' => $this->buildRequestPayload($amountRials, $orderId, $callbackUrl),
|
||||
'headers' => ['Content-Type' => 'text/xml; charset=utf-8'],
|
||||
'timeout' => 10,
|
||||
]
|
||||
);
|
||||
|
||||
$resCode = $this->parseResCode($response->getContent());
|
||||
|
||||
if ($resCode !== '0') {
|
||||
return new PaymentInitResult(false, errorMessage: "Mellat error: $resCode");
|
||||
}
|
||||
|
||||
$refId = $this->parseRefId($response->getContent());
|
||||
$redirectUrl = self::PAYMENT_URL . '?RefId=' . $refId;
|
||||
|
||||
return new PaymentInitResult(true, redirectUrl: $redirectUrl, token: $refId);
|
||||
} catch (\Throwable $e) {
|
||||
return new PaymentInitResult(false, errorMessage: $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function verify(array $callbackData): PaymentVerifyResult
|
||||
{
|
||||
$refId = $callbackData['RefId'] ?? '';
|
||||
$resCode = $callbackData['ResCode'] ?? '';
|
||||
|
||||
if ($resCode !== '0') {
|
||||
return new PaymentVerifyResult(false, errorMessage: "Payment failed: $resCode");
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request('POST',
|
||||
'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl', [
|
||||
'body' => $this->buildVerifyPayload($refId),
|
||||
'headers' => ['Content-Type' => 'text/xml; charset=utf-8'],
|
||||
'timeout' => 10,
|
||||
]
|
||||
);
|
||||
|
||||
$verifyCode = $this->parseResCode($response->getContent());
|
||||
if ($verifyCode !== '0') {
|
||||
return new PaymentVerifyResult(false, errorMessage: "Verify failed: $verifyCode");
|
||||
}
|
||||
|
||||
return new PaymentVerifyResult(true, referenceId: $refId);
|
||||
} catch (\Throwable $e) {
|
||||
return new PaymentVerifyResult(false, errorMessage: $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function buildRequestPayload(int $amount, string $orderId, string $callbackUrl): string
|
||||
{
|
||||
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>
|
||||
<orderId>{$orderId}</orderId>
|
||||
<amount>{$amount}</amount>
|
||||
<localDate>{$this->date()}</localDate>
|
||||
<localTime>{$this->time()}</localTime>
|
||||
<additionalData></additionalData>
|
||||
<callBackUrl>{$callbackUrl}</callBackUrl>
|
||||
<payerId>0</payerId>
|
||||
</int:bpPayRequest>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
XML;
|
||||
}
|
||||
|
||||
private function buildVerifyPayload(string $refId): string
|
||||
{
|
||||
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>
|
||||
<orderId>{$refId}</orderId>
|
||||
<saleOrderId>{$refId}</saleOrderId>
|
||||
<saleReferenceId>{$refId}</saleReferenceId>
|
||||
</int:bpVerifyRequest>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
XML;
|
||||
}
|
||||
|
||||
private function parseResCode(string $xml): string
|
||||
{
|
||||
preg_match('/<return>(.*?)<\/return>/', $xml, $m);
|
||||
$parts = explode(',', $m[1] ?? '');
|
||||
return trim($parts[0] ?? '-1');
|
||||
}
|
||||
|
||||
private function parseRefId(string $xml): string
|
||||
{
|
||||
preg_match('/<return>(.*?)<\/return>/', $xml, $m);
|
||||
$parts = explode(',', $m[1] ?? '');
|
||||
return trim($parts[1] ?? '');
|
||||
}
|
||||
|
||||
private function date(): string { return date('Ymd'); }
|
||||
private function time(): string { return date('His'); }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
interface PaymentGatewayInterface
|
||||
{
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Initiates payment, returns redirect URL or token.
|
||||
*/
|
||||
public function initiate(int $amountRials, string $orderId, string $callbackUrl): PaymentInitResult;
|
||||
|
||||
/**
|
||||
* Verifies callback and confirms payment.
|
||||
*/
|
||||
public function verify(array $callbackData): PaymentVerifyResult;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
final class PaymentInitResult
|
||||
{
|
||||
public function __construct(
|
||||
public readonly bool $success,
|
||||
public readonly string $redirectUrl = '',
|
||||
public readonly string $token = '',
|
||||
public readonly string $errorMessage = '',
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
final class PaymentVerifyResult
|
||||
{
|
||||
public function __construct(
|
||||
public readonly bool $success,
|
||||
public readonly string $referenceId = '',
|
||||
public readonly string $errorMessage = '',
|
||||
public readonly int $amountRials = 0,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class SepGateway implements PaymentGatewayInterface
|
||||
{
|
||||
private const TOKEN_URL = 'https://sep.shaparak.ir/onlinepg/onlinepg';
|
||||
private const PAYMENT_URL = 'https://sep.shaparak.ir/OnlinePG/OnlinePG';
|
||||
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly string $terminalId,
|
||||
) {}
|
||||
|
||||
public function getName(): string { return 'sep'; }
|
||||
|
||||
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,
|
||||
'Amount' => $amountRials,
|
||||
'ResNum' => $orderId,
|
||||
'RedirectUrl' => $callbackUrl,
|
||||
],
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
$data = $response->toArray();
|
||||
if (($data['status'] ?? -1) !== 1) {
|
||||
return new PaymentInitResult(false, errorMessage: $data['errorDesc'] ?? 'SEP error');
|
||||
}
|
||||
|
||||
$token = $data['token'];
|
||||
$redirectUrl = self::PAYMENT_URL . '?Token=' . $token . '&GetMethod=true';
|
||||
|
||||
return new PaymentInitResult(true, redirectUrl: $redirectUrl, token: $token);
|
||||
} catch (\Throwable $e) {
|
||||
return new PaymentInitResult(false, errorMessage: $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function verify(array $callbackData): PaymentVerifyResult
|
||||
{
|
||||
$state = $callbackData['State'] ?? '';
|
||||
if (strtolower($state) !== 'ok') {
|
||||
return new PaymentVerifyResult(false, errorMessage: "Payment state: $state");
|
||||
}
|
||||
|
||||
$refNum = $callbackData['RefNum'] ?? '';
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request('POST', self::TOKEN_URL, [
|
||||
'json' => [
|
||||
'action' => 'verify',
|
||||
'TerminalId' => $this->terminalId,
|
||||
'RefNum' => $refNum,
|
||||
],
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
$data = $response->toArray();
|
||||
if (($data['TransactionDetail']['AffectiveAmount'] ?? 0) <= 0) {
|
||||
return new PaymentVerifyResult(false, errorMessage: 'SEP verify failed');
|
||||
}
|
||||
|
||||
return new PaymentVerifyResult(
|
||||
true,
|
||||
referenceId: $refNum,
|
||||
amountRials: (int) $data['TransactionDetail']['AffectiveAmount']
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
return new PaymentVerifyResult(false, errorMessage: $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user