cfg('mellat_terminal_id', $this->terminalId) !== '' && $this->cfg('mellat_username', $this->username) !== '' && $this->cfg('mellat_password', $this->password) !== ''; } private function cfg(string $key, ?string $envFallback): string { return (string) ($this->configRepo->get($key) ?: $envFallback ?? ''); } 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; // درگاه ملت باید با POST فرم (فیلد RefId) باز شود؛ redirectUrl (شامل RefId) // برای سازگاری با مصرف‌کننده‌های قدیمی نگه داشته می‌شود. return new PaymentInitResult( true, redirectUrl: $redirectUrl, token: $refId, redirectMethod: 'POST', redirectParams: ['RefId' => $refId], ); } catch (\Throwable $e) { $this->logger->error(sprintf('Payment initiate failed (mellat): %s @ %s:%d', $e->getMessage(), $e->getFile(), $e->getLine()), ['exception' => $e, 'orderId' => $orderId, 'amount' => $amountRials]); return new PaymentInitResult(false, errorMessage: $e->getMessage()); } } public function verify(array $callbackData): PaymentVerifyResult { $refId = $callbackData['RefId'] ?? ''; $resCode = $callbackData['ResCode'] ?? ''; if ($resCode === '17') { return new PaymentVerifyResult(false, errorMessage: 'پرداخت توسط کاربر لغو شد', canceled: true); } 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) { $this->logger->error(sprintf('Payment verify failed (mellat): %s @ %s:%d', $e->getMessage(), $e->getFile(), $e->getLine()), ['exception' => $e, 'refId' => $refId]); return new PaymentVerifyResult(false, errorMessage: $e->getMessage()); } } 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 << {$terminalId} {$username} {$password} {$orderId} {$amount} {$this->date()} {$this->time()} {$callbackUrl} 0 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 << {$terminalId} {$username} {$password} {$refId} {$refId} {$refId} XML; } private function parseResCode(string $xml): string { preg_match('/(.*?)<\/return>/', $xml, $m); $parts = explode(',', $m[1] ?? ''); return trim($parts[0] ?? '-1'); } private function parseRefId(string $xml): string { preg_match('/(.*?)<\/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'); } }