Files
2026-09-15 10:35:56 +08:00

123 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\Oceanpayment\Model\Gateway;
use Esun\Oceanpayment\Model\Config;
use Esun\Oceanpayment\Model\Signature;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\HTTP\Client\CurlFactory;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
use Psr\Log\LoggerInterface;
class Client
{
public function __construct(
private readonly Config $config,
private readonly Signature $signature,
private readonly RequestBuilder $requestBuilder,
private readonly ResponseParser $responseParser,
private readonly CurlFactory $curlFactory,
private readonly OrderRepositoryInterface $orderRepository,
private readonly LoggerInterface $logger
) {
}
public function createPaymentUrl(Order $order): string
{
$storeId = (int)$order->getStoreId();
$method = (string)$order->getPayment()->getMethod();
if (!$this->config->isConfigured($storeId, $method)) {
throw new LocalizedException(__('Oceanpayment is not fully configured.'));
}
$request = $this->requestBuilder->build($order);
$curl = $this->curlFactory->create();
$curl->setTimeout(30);
$curl->addHeader('Accept', 'application/xml');
$curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
$curl->setOption(CURLOPT_FOLLOWLOCATION, false);
if ($this->config->isDebug($storeId)) {
$this->logger->debug('Oceanpayment create request', [
'order_number' => $request['order_number'],
'endpoint' => $this->config->getEndpoint($storeId, $method),
'sandbox' => $this->config->isSandbox($storeId, $method),
]);
}
try {
$curl->post($this->config->getEndpoint($storeId, $method), $request);
} catch (\Throwable $exception) {
$this->logger->error('Oceanpayment gateway request failed', [
'order_number' => $order->getIncrementId(),
'exception' => $exception,
]);
throw new LocalizedException(__('Unable to connect to Oceanpayment.'), $exception);
}
$status = $curl->getStatus();
if ($status < 200 || $status >= 300) {
throw new LocalizedException(__('Oceanpayment returned HTTP status %1.', $status));
}
$response = $this->responseParser->parse((string)$curl->getBody());
$this->validateResponse($order, $response);
$payment = $order->getPayment();
$payment->setAdditionalInformation('qianhai_payment_id', $response['payment_id']);
$payment->setAdditionalInformation('qianhai_pay_url', $response['pay_url']);
$payment->setAdditionalInformation('qianhai_create_result', $response['pay_details']);
$this->orderRepository->save($order);
return $response['pay_url'];
}
private function validateResponse(Order $order, array $response): void
{
$storeId = (int)$order->getStoreId();
$method = (string)$order->getPayment()->getMethod();
$account = $this->config->getSecret('account', $storeId, $method);
$terminal = $this->config->getSecret('terminal', $storeId, $method);
if (!hash_equals($account, $response['account'])
|| !hash_equals($terminal, $response['terminal'])
|| !hash_equals((string)$order->getIncrementId(), $response['order_number'])
|| !hash_equals(strtoupper((string)$order->getOrderCurrencyCode()), strtoupper($response['order_currency']))
|| !$this->amountsEqual((float)$order->getGrandTotal(), $response['order_amount'])
) {
throw new LocalizedException(__('Oceanpayment response did not match the Magento order.'));
}
if (!$this->signature->verifyCreateResponse(
$response,
$this->config->getSecret('secure_code', $storeId, $method)
)) {
throw new LocalizedException(__('Oceanpayment response signature verification failed.'));
}
if ($response['pay_results'] !== '1') {
throw new LocalizedException(__('Oceanpayment rejected the order: %1', $response['pay_details']));
}
if (!$this->isAllowedPaymentUrl($response['pay_url'])) {
throw new LocalizedException(__('Oceanpayment returned an invalid payment URL.'));
}
}
private function amountsEqual(float $expected, string $actual): bool
{
return abs($expected - (float)$actual) < 0.005;
}
public function isAllowedPaymentUrl(string $url): bool
{
$parts = parse_url($url);
$host = strtolower((string)($parts['host'] ?? ''));
return ($parts['scheme'] ?? '') === 'https'
&& ($host === 'oceanpayment.com' || str_ends_with($host, '.oceanpayment.com'));
}
}