feat(payment): update Mellat gateway implementation and enhance security checks
This commit is contained in:
@@ -9,6 +9,8 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
class MellatGateway implements PaymentGatewayInterface
|
||||
{
|
||||
private const PAYMENT_URL = 'https://bpm.shaparak.ir/pgwchannel/startpay.mellat';
|
||||
// endpoint سرویس SOAP (بدون ?wsdl؛ ?wsdl فقط توصیفِ سرویس است و POST به آن 500 میدهد).
|
||||
private const SERVICE_URL = 'https://bpm.shaparak.ir/pgwchannel/services/pgw';
|
||||
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
@@ -41,10 +43,10 @@ class MellatGateway implements PaymentGatewayInterface
|
||||
try {
|
||||
$response = $this->httpClient->request(
|
||||
'POST',
|
||||
'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl',
|
||||
self::SERVICE_URL,
|
||||
[
|
||||
'body' => $this->buildRequestPayload($amountRials, $orderId, $callbackUrl),
|
||||
'headers' => ['Content-Type' => 'text/xml; charset=utf-8'],
|
||||
'headers' => ['Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => '""'],
|
||||
'timeout' => 10,
|
||||
]
|
||||
);
|
||||
@@ -75,8 +77,9 @@ class MellatGateway implements PaymentGatewayInterface
|
||||
|
||||
public function verify(array $callbackData): PaymentVerifyResult
|
||||
{
|
||||
$refId = $callbackData['RefId'] ?? '';
|
||||
$resCode = $callbackData['ResCode'] ?? '';
|
||||
$resCode = $callbackData['ResCode'] ?? '';
|
||||
$saleOrderId = $callbackData['SaleOrderId'] ?? '';
|
||||
$saleReferenceId = $callbackData['SaleReferenceId'] ?? '';
|
||||
|
||||
if ($resCode === '17') {
|
||||
return new PaymentVerifyResult(false, errorMessage: 'پرداخت توسط کاربر لغو شد', canceled: true);
|
||||
@@ -86,25 +89,32 @@ class MellatGateway implements PaymentGatewayInterface
|
||||
return new PaymentVerifyResult(false, errorMessage: "Payment failed: $resCode");
|
||||
}
|
||||
|
||||
// برای تأیید و واریز، ملت به saleOrderId (همان orderId مرحلهٔ Pay) و
|
||||
// saleReferenceId (که در callback برمیگردد) نیاز دارد — نه RefId.
|
||||
if ($saleOrderId === '' || $saleReferenceId === '') {
|
||||
return new PaymentVerifyResult(false, errorMessage: 'اطلاعات بازگشتی درگاه ناقص است');
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request(
|
||||
'POST',
|
||||
'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl',
|
||||
self::SERVICE_URL,
|
||||
[
|
||||
'body' => $this->buildVerifyPayload($refId),
|
||||
'headers' => ['Content-Type' => 'text/xml; charset=utf-8'],
|
||||
'body' => $this->buildVerifySettlePayload($saleOrderId, $saleReferenceId),
|
||||
'headers' => ['Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => '""'],
|
||||
'timeout' => 10,
|
||||
]
|
||||
);
|
||||
|
||||
$verifyCode = $this->parseResCode($response->getContent());
|
||||
if ($verifyCode !== '0') {
|
||||
// 0 = موفق، 43 = پیشتر verify شده، 45 = پیشتر settle شده (هر دو idempotent = موفق).
|
||||
if (!in_array($verifyCode, ['0', '43', '45'], true)) {
|
||||
return new PaymentVerifyResult(false, errorMessage: "Verify failed: $verifyCode");
|
||||
}
|
||||
|
||||
return new PaymentVerifyResult(true, referenceId: $refId);
|
||||
return new PaymentVerifyResult(true, referenceId: $saleReferenceId);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error(sprintf('Payment verify failed (mellat): %s @ %s:%d', $e->getMessage(), $e->getFile(), $e->getLine()), ['exception' => $e, 'refId' => $refId]);
|
||||
$this->logger->error(sprintf('Payment verify failed (mellat): %s @ %s:%d', $e->getMessage(), $e->getFile(), $e->getLine()), ['exception' => $e, 'saleReferenceId' => $saleReferenceId]);
|
||||
return new PaymentVerifyResult(false, errorMessage: $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -135,7 +145,8 @@ class MellatGateway implements PaymentGatewayInterface
|
||||
XML;
|
||||
}
|
||||
|
||||
private function buildVerifyPayload(string $refId): string
|
||||
/** تأیید و واریز یکجا (bpVerifySettleRequest). orderId میتواند برابر saleOrderId باشد. */
|
||||
private function buildVerifySettlePayload(string $saleOrderId, string $saleReferenceId): string
|
||||
{
|
||||
$terminalId = $this->cfg('mellat_terminal_id', $this->terminalId);
|
||||
$username = $this->cfg('mellat_username', $this->username);
|
||||
@@ -144,14 +155,14 @@ XML;
|
||||
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>
|
||||
<int:bpVerifySettleRequest>
|
||||
<terminalId>{$terminalId}</terminalId>
|
||||
<userName>{$username}</userName>
|
||||
<userPassword>{$password}</userPassword>
|
||||
<orderId>{$refId}</orderId>
|
||||
<saleOrderId>{$refId}</saleOrderId>
|
||||
<saleReferenceId>{$refId}</saleReferenceId>
|
||||
</int:bpVerifyRequest>
|
||||
<orderId>{$saleOrderId}</orderId>
|
||||
<saleOrderId>{$saleOrderId}</saleOrderId>
|
||||
<saleReferenceId>{$saleReferenceId}</saleReferenceId>
|
||||
</int:bpVerifySettleRequest>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
XML;
|
||||
|
||||
Reference in New Issue
Block a user