check = new CheckHandler($repository); $this->pay = new PayHandler( repository: $repository, deliverOrder: $deliverOrder ?? helper::deliver_order(...), notifier: $notifier, currentPrice: $currentPrice, logger: $logger, ); } /** * @param array $fields */ public function handle(array $fields): CallbackResponse { try { $notification = new CallbackNotification($fields); } catch (\InvalidArgumentException) { return CallbackResponse::fail(); } $merchantOrderId = AssistantTransactionId::parse($notification->getTransactionId()); if ($merchantOrderId === null) { return CallbackResponse::fail(); } $order = $this->repository->findByMerchantOrderId($merchantOrderId); if ($order === null) { return $this->unknownOrder($notification); } $config = $this->configs->forAccount($order->accountId); if ($config === null || !$config->isComplete()) { return CallbackResponse::fail(); } if ( $notification->getAccountId() !== $config->accountNumber || $notification->getAccountId() !== $order->snapshot->accountNumber || !$notification->isSignedBy($config->signature()) ) { $this->logger->error('Уведомление отклонено: подпись или номер счёта не совпадают.'); return CallbackResponse::fail(); } $lock = lock_config::get_lock_factory('paygw_moneta')->get_lock($order->merchantOrderId, self::LOCK_TIMEOUT); if (!$lock) { return CallbackResponse::fail(); } try { $order = $this->repository->findById($order->id) ?? $order; return $this->handleSigned($notification, $order, $config); } finally { $lock->release(); } } private function handleSigned(CallbackNotification $notification, Order $order, GatewayConfig $config): CallbackResponse { $error = $this->validate($notification, $order); if ($error !== null) { $this->repository->noteError($order, $error); return CallbackResponse::fail(); } $handler = $notification->isCheck() ? $this->check : $this->pay; return $handler->handle($notification, $order, $config); } private function validate(CallbackNotification $notification, Order $order): ?RejectionReason { if ($notification->getCurrency() !== $order->currency) { return RejectionReason::Currency; } if ($notification->isTestMode() !== $order->snapshot->testMode) { return RejectionReason::TestMode; } if ($notification->getSubscriberId() !== '' && $notification->getSubscriberId() !== $order->subscriberId()) { return RejectionReason::Subscriber; } $amount = $notification->getAmount(); if ($amount !== null && !$amount->equals($order->amount)) { return RejectionReason::Amount; } return null; } private function unknownOrder(CallbackNotification $notification): CallbackResponse { $config = $this->configs->findByAccountNumber($notification->getAccountId()); if ($config === null || !$notification->isSignedBy($config->signature())) { return CallbackResponse::fail(); } if (!$notification->isCheck()) { $this->logger->error( sprintf( 'Оплачено уведомление по неизвестному заказу %s (операция %s).', $notification->getTransactionId(), $notification->getOperationId(), ), ); return CallbackResponse::fail(); } $response = new CheckResponse( accountId: $config->accountNumber, transactionId: $notification->getTransactionId(), amount: $notification->getAmount() ?? Money::fromMinorUnits(0), resultCode: ResultCode::Rejected, signature: $config->signature(), cms: CmsInfo::getCmsModuleVersion(), ); return $response->toXml(); } }