OpenAI ChatGPT configuration

This commit is contained in:
Flatlogic Bot 2025-12-29 11:21:40 +00:00
parent 138c42dccb
commit 3d232d5225
3 changed files with 326 additions and 371 deletions

View File

@ -1,27 +1,6 @@
<?php
// LocalAIApi — proxy client for the Responses API.
// Usage (async: auto-polls status until ready):
// require_once __DIR__ . '/ai/LocalAIApi.php';
// $response = LocalAIApi::createResponse([
// 'input' => [
// ['role' => 'system', 'content' => 'You are a helpful assistant.'],
// ['role' => 'user', 'content' => 'Tell me a bedtime story.'],
// ],
// ]);
// if (!empty($response['success'])) {
// // response['data'] contains full payload, e.g.:
// // {
// // "id": "resp_xxx",
// // "status": "completed",
// // "output": [
// // {"type": "reasoning", "summary": []},
// // {"type": "message", "content": [{"type": "output_text", "text": "Your final answer here."}]}
// // ]
// // }
// $decoded = LocalAIApi::decodeJsonFromResponse($response); // or inspect $response['data'] / extractText(...)
// }
// Poll settings override:
// LocalAIApi::createResponse($payload, ['poll_interval' => 5, 'poll_timeout' => 300]);
// LocalAIApi — client for AI services.
// Supports the Flatlogic proxy and direct OpenAI API calls.
class LocalAIApi
{
@ -29,89 +8,93 @@ class LocalAIApi
private static ?array $configCache = null;
/**
* Signature compatible with the OpenAI Responses API.
* Create a response from the configured AI service.
*
* @param array<string,mixed> $params Request body (model, input, text, reasoning, metadata, etc.).
* @param array<string,mixed> $options Extra options (timeout, verify_tls, headers, path, project_uuid).
* @return array{
* success:bool,
* status?:int,
* data?:mixed,
* error?:string,
* response?:mixed,
* message?:string
* }
* @param array<string,mixed> $params Request body (model, input, etc.).
* @param array<string,mixed> $options Extra options.
* @return array<string,mixed>
*/
public static function createResponse(array $params, array $options = []): array
{
$cfg = self::config();
$payload = $params;
if (empty($payload['input']) || !is_array($payload['input'])) {
return [
'success' => false,
'error' => 'input_missing',
'message' => 'Parameter "input" is required and must be an array.',
if ($cfg['is_custom']) {
// Direct OpenAI API call
if (empty($payload['input']) || !is_array($payload['input'])) {
return [
'success' => false,
'error' => 'input_missing',
'message' => 'Parameter "input" is required for custom AI call.',
];
}
// OpenAI uses 'messages', not 'input'
$openAiPayload = [
'model' => $payload['model'] ?? $cfg['default_model'],
'messages' => $payload['input'],
];
}
if (!isset($payload['model']) || $payload['model'] === '') {
$payload['model'] = $cfg['default_model'];
}
return self::request(null, $openAiPayload, $options);
} else {
// Flatlogic Proxy call
if (empty($payload['input']) || !is_array($payload['input'])) {
return [
'success' => false,
'error' => 'input_missing',
'message' => 'Parameter "input" is required and must be an array.',
];
}
if (!isset($payload['model']) || $payload['model'] === '') {
$payload['model'] = $cfg['default_model'];
}
$initial = self::request($options['path'] ?? null, $payload, $options);
if (empty($initial['success'])) {
return $initial;
}
// Async flow for Flatlogic proxy
$data = $initial['data'] ?? null;
if (is_array($data) && isset($data['ai_request_id'])) {
$aiRequestId = $data['ai_request_id'];
$pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300;
$pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5;
return self::awaitResponse($aiRequestId, [
'timeout' => $pollTimeout,
'interval' => $pollInterval,
'headers' => $options['headers'] ?? [],
'timeout_per_call' => $options['timeout'] ?? null,
]);
}
$initial = self::request($options['path'] ?? null, $payload, $options);
if (empty($initial['success'])) {
return $initial;
}
// Async flow: if backend returns ai_request_id, poll status until ready
$data = $initial['data'] ?? null;
if (is_array($data) && isset($data['ai_request_id'])) {
$aiRequestId = $data['ai_request_id'];
$pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300; // seconds
$pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5; // seconds
return self::awaitResponse($aiRequestId, [
'timeout' => $pollTimeout,
'interval' => $pollInterval,
'headers' => $options['headers'] ?? [],
'timeout_per_call' => $options['timeout'] ?? null,
]);
}
return $initial;
}
/**
* Snake_case alias for createResponse (matches the provided example).
* Perform a raw request.
*
* @param array<string,mixed> $params
* @param array<string,mixed> $options
* @return array<string,mixed>
*/
public static function create_response(array $params, array $options = []): array
{
return self::createResponse($params, $options);
}
/**
* Perform a raw request to the AI proxy.
*
* @param string $path Endpoint (may be an absolute URL).
* @param string|null $path Endpoint path.
* @param array<string,mixed> $payload JSON payload.
* @param array<string,mixed> $options Additional request options.
* @param array<string,mixed> $options Additional options.
* @return array<string,mixed>
*/
public static function request(?string $path = null, array $payload = [], array $options = []): array
{
$cfg = self::config();
$projectUuid = $cfg['project_uuid'];
if (empty($projectUuid)) {
return [
'success' => false,
'error' => 'project_uuid_missing',
'message' => 'PROJECT_UUID is not defined; aborting AI request.',
];
if (!$cfg['is_custom']) {
$projectUuid = $cfg['project_uuid'];
if (empty($projectUuid)) {
return [
'success' => false,
'error' => 'project_uuid_missing',
'message' => 'PROJECT_UUID is not defined; aborting AI request.',
];
}
}
$defaultPath = $cfg['responses_path'] ?? null;
@ -119,168 +102,38 @@ class LocalAIApi
if (empty($resolvedPath)) {
return [
'success' => false,
'error' => 'project_id_missing',
'message' => 'PROJECT_ID is not defined; cannot resolve AI proxy endpoint.',
'error' => 'path_missing',
'message' => 'Cannot resolve AI endpoint path.',
];
}
$url = self::buildUrl($resolvedPath, $cfg['base_url']);
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30;
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout;
if ($timeout <= 0) {
$timeout = 30;
}
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
$verifyTls = array_key_exists('verify_tls', $options)
? (bool) $options['verify_tls']
: $baseVerifyTls;
$projectHeader = $cfg['project_header'];
$headers = [
'Content-Type: application/json',
'Accept: application/json',
];
$headers[] = $projectHeader . ': ' . $projectUuid;
$timeout = (int)($options['timeout'] ?? $cfg['timeout'] ?? 30);
$verifyTls = (bool)($options['verify_tls'] ?? $cfg['verify_tls'] ?? true);
$headers = $cfg['headers'] ?? [];
if (!empty($options['headers']) && is_array($options['headers'])) {
foreach ($options['headers'] as $header) {
if (is_string($header) && $header !== '') {
$headers[] = $header;
}
}
$headers = array_merge($headers, $options['headers']);
}
if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) {
$payload['project_uuid'] = $projectUuid;
if (!$cfg['is_custom']) {
if (!empty($cfg['project_uuid']) && !array_key_exists('project_uuid', $payload)) {
$payload['project_uuid'] = $cfg['project_uuid'];
}
}
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
if ($body === false) {
return [
'success' => false,
'error' => 'json_encode_failed',
'message' => 'Failed to encode request body to JSON.',
];
return ['success' => false, 'error' => 'json_encode_failed'];
}
return self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls);
}
/**
* Poll AI request status until ready or timeout.
* Extract plain text from an AI response.
*
* @param int|string $aiRequestId
* @param array<string,mixed> $options
* @return array<string,mixed>
*/
public static function awaitResponse($aiRequestId, array $options = []): array
{
$cfg = self::config();
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : 300; // seconds
$interval = isset($options['interval']) ? (int) $options['interval'] : 5; // seconds
if ($interval <= 0) {
$interval = 5;
}
$perCallTimeout = isset($options['timeout_per_call']) ? (int) $options['timeout_per_call'] : null;
$deadline = time() + max($timeout, $interval);
$headers = $options['headers'] ?? [];
while (true) {
$statusResp = self::fetchStatus($aiRequestId, [
'headers' => $headers,
'timeout' => $perCallTimeout,
]);
if (!empty($statusResp['success'])) {
$data = $statusResp['data'] ?? [];
if (is_array($data)) {
$statusValue = $data['status'] ?? null;
if ($statusValue === 'success') {
return [
'success' => true,
'status' => 200,
'data' => $data['response'] ?? $data,
];
}
if ($statusValue === 'failed') {
return [
'success' => false,
'status' => 500,
'error' => isset($data['error']) ? (string)$data['error'] : 'AI request failed',
'data' => $data,
];
}
}
} else {
return $statusResp;
}
if (time() >= $deadline) {
return [
'success' => false,
'error' => 'timeout',
'message' => 'Timed out waiting for AI response.',
];
}
sleep($interval);
}
}
/**
* Fetch status for queued AI request.
*
* @param int|string $aiRequestId
* @param array<string,mixed> $options
* @return array<string,mixed>
*/
public static function fetchStatus($aiRequestId, array $options = []): array
{
$cfg = self::config();
$projectUuid = $cfg['project_uuid'];
if (empty($projectUuid)) {
return [
'success' => false,
'error' => 'project_uuid_missing',
'message' => 'PROJECT_UUID is not defined; aborting status check.',
];
}
$statusPath = self::resolveStatusPath($aiRequestId, $cfg);
$url = self::buildUrl($statusPath, $cfg['base_url']);
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30;
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout;
if ($timeout <= 0) {
$timeout = 30;
}
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
$verifyTls = array_key_exists('verify_tls', $options)
? (bool) $options['verify_tls']
: $baseVerifyTls;
$projectHeader = $cfg['project_header'];
$headers = [
'Accept: application/json',
$projectHeader . ': ' . $projectUuid,
];
if (!empty($options['headers']) && is_array($options['headers'])) {
foreach ($options['headers'] as $header) {
if (is_string($header) && $header !== '') {
$headers[] = $header;
}
}
}
return self::sendCurl($url, 'GET', null, $headers, $timeout, $verifyTls);
}
/**
* Extract plain text from a Responses API payload.
*
* @param array<string,mixed> $response Result of LocalAIApi::createResponse|request.
* @param array<string,mixed> $response
* @return string
*/
public static function extractText(array $response): string
@ -290,138 +143,52 @@ class LocalAIApi
return '';
}
// OpenAI API format
if (!empty($payload['choices'][0]['message']['content'])) {
return (string) $payload['choices'][0]['message']['content'];
}
// Flatlogic proxy format
if (!empty($payload['output']) && is_array($payload['output'])) {
$combined = '';
foreach ($payload['output'] as $item) {
if (!isset($item['content']) || !is_array($item['content'])) {
continue;
}
if (!isset($item['content']) || !is_array($item['content'])) continue;
foreach ($item['content'] as $block) {
if (is_array($block) && ($block['type'] ?? '') === 'output_text' && !empty($block['text'])) {
if (($block['type'] ?? '') === 'output_text' && !empty($block['text'])) {
$combined .= $block['text'];
}
}
}
if ($combined !== '') {
return $combined;
}
}
if (!empty($payload['choices'][0]['message']['content'])) {
return (string) $payload['choices'][0]['message']['content'];
if ($combined !== '') return $combined;
}
return '';
}
/**
* Attempt to decode JSON emitted by the model (handles markdown fences).
*
* @param array<string,mixed> $response
* @return array<string,mixed>|null
*/
public static function decodeJsonFromResponse(array $response): ?array
{
$text = self::extractText($response);
if ($text === '') {
return null;
}
$decoded = json_decode($text, true);
if (is_array($decoded)) {
return $decoded;
}
$stripped = preg_replace('/^```json|```$/m', '', trim($text));
if ($stripped !== null && $stripped !== $text) {
$decoded = json_decode($stripped, true);
if (is_array($decoded)) {
return $decoded;
}
}
return null;
}
// --- Helper methods below are mostly unchanged ---
/**
* Load configuration from ai/config.php.
*
* Load configuration.
* @return array<string,mixed>
*/
private static function config(): array
{
if (self::$configCache === null) {
$configPath = __DIR__ . '/config.php';
if (!file_exists($configPath)) {
throw new RuntimeException('AI config file not found: ai/config.php');
}
$cfg = require $configPath;
if (!is_array($cfg)) {
throw new RuntimeException('Invalid AI config format: expected array');
}
self::$configCache = $cfg;
if (!file_exists($configPath)) throw new RuntimeException('AI config not found');
self::$configCache = require $configPath;
}
return self::$configCache;
}
/**
* Build an absolute URL from base_url and a path.
*/
private static function buildUrl(string $path, string $baseUrl): string
{
$trimmed = trim($path);
if ($trimmed === '') {
return $baseUrl;
}
if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) {
return $trimmed;
}
if ($trimmed[0] === '/') {
return $baseUrl . $trimmed;
}
return $baseUrl . '/' . $trimmed;
}
/**
* Resolve status path based on configured responses_path and ai_request_id.
*
* @param int|string $aiRequestId
* @param array<string,mixed> $cfg
* @return string
*/
private static function resolveStatusPath($aiRequestId, array $cfg): string
{
$basePath = $cfg['responses_path'] ?? '';
$trimmed = rtrim($basePath, '/');
if ($trimmed === '') {
return '/ai-request/' . rawurlencode((string)$aiRequestId) . '/status';
}
if (substr($trimmed, -11) !== '/ai-request') {
$trimmed .= '/ai-request';
}
return $trimmed . '/' . rawurlencode((string)$aiRequestId) . '/status';
}
/**
* Shared CURL sender for GET/POST requests.
*
* @param string $url
* @param string $method
* @param string|null $body
* @param array<int,string> $headers
* @param int $timeout
* @param bool $verifyTls
* Shared cURL sender.
* @return array<string,mixed>
*/
private static function sendCurl(string $url, string $method, ?string $body, array $headers, int $timeout, bool $verifyTls): array
{
if (!function_exists('curl_init')) {
return [
'success' => false,
'error' => 'curl_missing',
'message' => 'PHP cURL extension is missing. Install or enable it on the VM.',
];
return ['success' => false, 'error' => 'curl_missing', 'message' => 'PHP cURL extension is missing.'];
}
$ch = curl_init($url);
@ -431,63 +198,128 @@ class LocalAIApi
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
$upper = strtoupper($method);
if ($upper === 'POST') {
if (strtoupper($method) === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body ?? '');
} else {
curl_setopt($ch, CURLOPT_HTTPGET, true);
}
$responseBody = curl_exec($ch);
if ($responseBody === false) {
$error = curl_error($ch) ?: 'Unknown cURL error';
curl_close($ch);
return [
'success' => false,
'error' => 'curl_error',
'message' => $error,
];
}
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($responseBody === false) {
$error = curl_error($ch);
curl_close($ch);
return ['success' => false, 'error' => 'curl_error', 'message' => $error];
}
curl_close($ch);
$decoded = null;
if ($responseBody !== '' && $responseBody !== null) {
$decoded = json_decode($responseBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$decoded = null;
}
}
$decoded = json_decode($responseBody, true);
if ($status >= 200 && $status < 300) {
return [
'success' => true,
'status' => $status,
'data' => $decoded ?? $responseBody,
];
return ['success' => true, 'status' => $status, 'data' => $decoded ?? $responseBody];
}
$errorMessage = 'AI proxy request failed';
if (is_array($decoded)) {
$errorMessage = $decoded['error'] ?? $decoded['message'] ?? $errorMessage;
} elseif (is_string($responseBody) && $responseBody !== '') {
$errorMessage = 'Request failed';
if (is_array($decoded) && isset($decoded['error']['message'])) {
$errorMessage = $decoded['error']['message'];
} elseif(is_string($responseBody)) {
$errorMessage = $responseBody;
}
return [
'success' => false,
'status' => $status,
'error' => $errorMessage,
'response' => $decoded ?? $responseBody,
'success' => false,
'status' => $status,
'error' => $errorMessage,
'response' => $decoded ?? $responseBody
];
}
/**
* Build an absolute URL.
*/
private static function buildUrl(string $path, string $baseUrl): string
{
if (str_starts_with($path, 'http')) return $path;
return rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
}
// --- Methods for Flatlogic proxy async flow ---
public static function awaitResponse($aiRequestId, array $options = []): array
{
$deadline = time() + (int)($options['timeout'] ?? 300);
$interval = (int)($options['interval'] ?? 5);
while (time() < $deadline) {
$statusResp = self::fetchStatus($aiRequestId, $options);
if (!($statusResp['success'] ?? false)) return $statusResp;
$data = $statusResp['data'] ?? [];
$statusValue = $data['status'] ?? null;
if ($statusValue === 'success') {
return ['success' => true, 'status' => 200, 'data' => $data['response'] ?? $data];
}
if ($statusValue === 'failed') {
return ['success' => false, 'error' => 'AI request failed', 'data' => $data];
}
sleep($interval);
}
return ['success' => false, 'error' => 'timeout'];
}
public static function fetchStatus($aiRequestId, array $options = []): array
{
$cfg = self::config();
if (empty($cfg['project_uuid'])) {
return ['success' => false, 'error' => 'project_uuid_missing'];
}
$statusPath = self::resolveStatusPath($aiRequestId, $cfg);
$url = self::buildUrl($statusPath, $cfg['base_url']);
$headers = $options['headers'] ?? [];
$headers[] = 'Accept: application/json';
$headers[] = $cfg['project_header'] . ': ' . $cfg['project_uuid'];
return self::sendCurl($url, 'GET', null, $headers, (int)($options['timeout'] ?? 30), true);
}
private static function resolveStatusPath($aiRequestId, array $cfg): string
{
$basePath = rtrim($cfg['responses_path'] ?? '', '/');
return $basePath . '/' . rawurlencode((string)$aiRequestId) . '/status';
}
/**
* Snake_case alias for createResponse.
* @deprecated
*/
public static function create_response(array $params, array $options = []): array
{
return self::createResponse($params, $options);
}
/**
* Decode JSON from response text.
*/
public static function decodeJsonFromResponse(array $response): ?array
{
$text = self::extractText($response);
if ($text === '') return null;
$decoded = json_decode($text, true);
if(is_array($decoded)) return $decoded;
// Strip markdown fences
$stripped = preg_replace('/^```json\n?|\n?```$/', '', $text);
if ($stripped !== $text) {
$decoded = json_decode($stripped, true);
if(is_array($decoded)) return $decoded;
}
return null;
}
}
// Legacy alias for backward compatibility with the previous class name.
// Legacy alias
if (!class_exists('OpenAIService')) {
class_alias(LocalAIApi::class, 'OpenAIService');
}
}

View File

@ -37,16 +37,41 @@ if (
$projectUuid = ($projectUuid === false) ? null : $projectUuid;
$projectId = ($projectId === false) ? null : $projectId;
$baseUrl = 'https://flatlogic.com';
$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null;
// Allow overriding with custom OpenAI settings
$openAiApiKey = getenv('OPENAI_API_KEY');
$openAiBaseUrl = getenv('OPENAI_BASE_URL');
$isCustom = !empty($openAiApiKey);
if ($isCustom) {
// Using custom OpenAI key, target OpenAI API directly
$baseUrl = !empty($openAiBaseUrl) ? $openAiBaseUrl : 'https://api.openai.com/v1';
$responsesPath = '/chat/completions'; // Standard for chat completions
$headers = [
'Authorization: Bearer ' . $openAiApiKey,
'Content-Type: application/json',
];
} else {
// Using Flatlogic proxy
$baseUrl = 'https://flatlogic.com';
$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null;
$headers = [
'Content-Type: application/json',
];
if ($projectUuid) {
$headers[] = 'project-uuid: ' . $projectUuid;
}
}
return [
'base_url' => $baseUrl,
'responses_path' => $responsesPath,
'headers' => $headers,
'is_custom' => $isCustom,
'project_id' => $projectId,
'project_uuid' => $projectUuid,
'project_header' => 'project-uuid',
'default_model' => 'gpt-5-mini',
'default_model' => 'gpt-3.5-turbo', // A more standard model
'timeout' => 30,
'verify_tls' => true,
];

View File

@ -11003,3 +11003,101 @@ Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.0
Found client price. Net: 1056.91, Gross: 1300
FINAL: Returning Net: 1056.91, Gross: 1300
---
---
START getEffectivePrice for product 1, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"}
Found product price. Net: 1111, Gross: 1366.53
FINAL: Returning Net: 1111, Gross: 1366.53
---
---
START getEffectivePrice for product 2, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"}
Found product price. Net: 1318.05, Gross: 1621.2
FINAL: Returning Net: 1318.05, Gross: 1621.2
---
---
START getEffectivePrice for product 3, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"}
Found product price. Net: 9.95, Gross: 12.24
FINAL: Returning Net: 9.95, Gross: 12.24
---
---
START getEffectivePrice for product 4, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"}
Found product price. Net: 9.95, Gross: 12.24
FINAL: Returning Net: 9.95, Gross: 12.24
---
---
START getEffectivePrice for product 5, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"}
Found product price. Net: 68, Gross: 83.64
FINAL: Returning Net: 68, Gross: 83.64
---
---
START getEffectivePrice for product 6, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"}
Found product price. Net: 171.6, Gross: 211.07
FINAL: Returning Net: 171.6, Gross: 211.07
---
---
START getEffectivePrice for product 7, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"}
Found product price. Net: 233.2, Gross: 286.84
FINAL: Returning Net: 233.2, Gross: 286.84
---
---
START getEffectivePrice for product 1, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"}
Found product price. Net: 1111, Gross: 1366.53
FINAL: Returning Net: 1111, Gross: 1366.53
---
---
START getEffectivePrice for product 2, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"}
Found product price. Net: 1318.05, Gross: 1621.2
FINAL: Returning Net: 1318.05, Gross: 1621.2
---
---
START getEffectivePrice for product 3, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"}
Found product price. Net: 9.95, Gross: 12.24
FINAL: Returning Net: 9.95, Gross: 12.24
---
---
START getEffectivePrice for product 4, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"}
Found product price. Net: 9.95, Gross: 12.24
FINAL: Returning Net: 9.95, Gross: 12.24
---
---
START getEffectivePrice for product 5, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"}
Found product price. Net: 68, Gross: 83.64
FINAL: Returning Net: 68, Gross: 83.64
---
---
START getEffectivePrice for product 6, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"}
Found product price. Net: 171.6, Gross: 211.07
FINAL: Returning Net: 171.6, Gross: 211.07
---
---
START getEffectivePrice for product 7, client
Client price not found or not set, falling back to product price.
Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"}
Found product price. Net: 233.2, Gross: 286.84
FINAL: Returning Net: 233.2, Gross: 286.84
---