orderFactory->create()->loadByIncrementId($orderNumber); if (!$order->getEntityId() || !Config::isSupportedMethod((string)$order->getPayment()->getMethod()) ) { throw new LocalizedException(__('The Oceanpayment order could not be found.')); } $this->validate($order, $data); $payment = $order->getPayment(); $paymentId = (string)$data['payment_id']; $status = (string)$data['payment_status']; $method = (string)$payment->getMethod(); if ((string)$payment->getAdditionalInformation('qianhai_processed_payment_id') === $paymentId && (string)$payment->getAdditionalInformation('qianhai_processed_status') === $status ) { return $this->mapStatus($status); } $payment->setAdditionalInformation('qianhai_payment_id', $paymentId); $payment->setAdditionalInformation('qianhai_processed_payment_id', $paymentId); $payment->setAdditionalInformation('qianhai_processed_status', $status); $payment->setAdditionalInformation('qianhai_payment_details', (string)$data['payment_details']); $payment->setAdditionalInformation('qianhai_payment_method', (string)$data['methods']); $payment->setTransactionId($paymentId); $payment->setLastTransId($paymentId); $result = $this->mapStatus($status); if ($result === self::SUCCESS) { $this->processSuccess($order, $data); } elseif ($result === self::PENDING) { $this->processPending($order, $data); } else { $this->processFailure($order, $data, $isAsync); } $this->orderRepository->save($order); if ($this->config->isDebug((int)$order->getStoreId(), $method)) { $this->logger->debug('Oceanpayment callback processed', [ 'order_number' => $orderNumber, 'payment_id' => $paymentId, 'payment_status' => $status, 'async' => $isAsync, ]); } return $result; } private function validate(Order $order, array $data): void { $storeId = (int)$order->getStoreId(); $method = (string)$order->getPayment()->getMethod(); if (!hash_equals($this->config->getSecret('account', $storeId, $method), (string)$data['account']) || !hash_equals($this->config->getSecret('terminal', $storeId, $method), (string)$data['terminal']) ) { throw new LocalizedException(__('Oceanpayment callback account verification failed.')); } if (!hash_equals( strtoupper((string)$order->getOrderCurrencyCode()), strtoupper((string)$data['order_currency']) ) || abs((float)$order->getGrandTotal() - (float)$data['order_amount']) >= 0.005) { throw new LocalizedException(__('Oceanpayment callback amount verification failed.')); } $secureCode = $this->config->getSecret('secure_code', $storeId, $method); if (!$this->signature->verifyCallback($data, $secureCode)) { if ($this->config->isDebug($storeId, $method)) { $this->logger->warning( 'Oceanpayment callback signature verification failed', $this->getSignatureDiagnostics($data, $secureCode) ); } throw new LocalizedException(__('Oceanpayment callback signature verification failed.')); } $expectedPaymentId = (string)$order->getPayment() ->getAdditionalInformation('qianhai_payment_id'); if ($expectedPaymentId !== '' && !hash_equals($expectedPaymentId, (string)$data['payment_id']) ) { throw new LocalizedException(__('Oceanpayment callback payment ID verification failed.')); } if (!in_array((string)$data['payment_status'], ['-1', '0', '1'], true)) { throw new LocalizedException(__('Oceanpayment returned an unknown payment status.')); } } private function getSignatureDiagnostics(array $data, string $secureCode): array { $provided = strtolower(trim((string)($data['signValue'] ?? ''))); $expected = $this->signature->createCallback($data, $secureCode); return [ 'account' => $this->mask((string)($data['account'] ?? '')), 'terminal' => $this->mask((string)($data['terminal'] ?? '')), 'order_number' => (string)($data['order_number'] ?? ''), 'order_currency' => (string)($data['order_currency'] ?? ''), 'order_amount' => (string)($data['order_amount'] ?? ''), 'order_notes_length' => strlen((string)($data['order_notes'] ?? '')), 'card_number' => $this->mask((string)($data['card_number'] ?? '')), 'payment_id' => (string)($data['payment_id'] ?? ''), 'payment_auth_type' => (string)($data['payment_authType'] ?? ''), 'payment_status' => (string)($data['payment_status'] ?? ''), 'payment_details' => mb_substr((string)($data['payment_details'] ?? ''), 0, 200), 'payment_risk' => mb_substr((string)($data['payment_risk'] ?? ''), 0, 200), 'provided_sign_prefix' => substr($provided, 0, 12), 'provided_sign_length' => strlen($provided), 'expected_sign_prefix' => substr($expected, 0, 12), ]; } private function mask(string $value): string { $length = strlen($value); if ($length <= 4) { return str_repeat('*', $length); } return str_repeat('*', $length - 4) . substr($value, -4); } private function processSuccess(Order $order, array $data): void { $payment = $order->getPayment(); $method = (string)$payment->getMethod(); $payment->setIsTransactionClosed(true); if ($order->canInvoice() && (float)$order->getTotalDue() > 0.0001) { $payment->registerCaptureNotification((float)$data['order_amount'], true); } $this->statusManager->apply( $order, $this->config->getValue('success_order_status', (int)$order->getStoreId(), $method), Order::STATE_PROCESSING ); $order->addCommentToStatusHistory( __('Oceanpayment payment approved. Payment ID: %1', $data['payment_id']) ); } private function processPending(Order $order, array $data): void { $method = (string)$order->getPayment()->getMethod(); $order->getPayment()->setIsTransactionClosed(false)->setIsTransactionPending(true); $this->statusManager->apply( $order, $this->config->getValue('pending_order_status', (int)$order->getStoreId(), $method), Order::STATE_PAYMENT_REVIEW ); $order->addCommentToStatusHistory( __('Oceanpayment payment is pending. Payment ID: %1', $data['payment_id']) ); } private function processFailure(Order $order, array $data, bool $isAsync): void { $method = (string)$order->getPayment()->getMethod(); $order->getPayment()->setIsTransactionClosed(true); if ($isAsync && $order->canCancel()) { $order->cancel(); $this->statusManager->apply( $order, $this->config->getValue('failure_order_status', (int)$order->getStoreId(), $method), Order::STATE_CANCELED ); } $order->addCommentToStatusHistory( __( 'Oceanpayment payment failed. Payment ID: %1. Details: %2', $data['payment_id'], $data['payment_details'] ) ); } private function mapStatus(string $status): string { return match ($status) { '1' => self::SUCCESS, '-1' => self::PENDING, default => self::FAILURE, }; } }