function mk_sym_products() {
$map = [];
if (MK_SYM_PRODUCT_ID > 0) $map[MK_SYM_PRODUCT_ID] = MK_SYM_SESSIONS;
if (MK_SYM_EXT_PRODUCT_ID > 0) $map[MK_SYM_EXT_PRODUCT_ID] = MK_SYM_EXT_SESSIONS;
if (MK_SYM_BUNDLE_PRODUCT_ID > 0) $map[MK_SYM_BUNDLE_PRODUCT_ID] = MK_SYM_BUNDLE_SESSIONS;
return $map;
}

// Suma sesji z produktow symulatora w zamowieniu (0 = brak produktu symulatora)
function mk_sym_order_sessions($order) {
$map = mk_sym_products();
$total = 0;
foreach ($order->get_items() as $item) {
$pid = (int) $item->get_product_id();
if (isset($map[$pid])) {
$total += $map[$pid] * max(1, (int) $item->get_quantity());
}
}
return $total;
}

// Fakturownia – uzupelnij po zdobyciu API tokena
if (!defined(‘MK_FAKTUROWNIA_TOKEN’)) define(‘MK_FAKTUROWNIA_TOKEN’, ‘hIjg6no2Q8DHgSqnu5Xv’);
if (!defined(‘MK_FAKTUROWNIA_DOMAIN’)) define(‘MK_FAKTUROWNIA_DOMAIN’, ‘czerwonka’);

// — REST endpoints —
add_action(‘rest_api_init’, function () {
register_rest_route(‘mk/v1’, ‘/symulator’, [
‘methods’ => ‘POST’,
‘callback’ => ‘mk_symulator_handler’,
‘permission_callback’ => ‘__return_true’,
]);
register_rest_route(‘mk/v1’, ‘/symulator-start’, [
‘methods’ => ‘POST’,
‘callback’ => ‘mk_symulator_start_handler’,
‘permission_callback’ => ‘__return_true’,
]);
register_rest_route(‘mk/v1’, ‘/symulator-report’, [
‘methods’ => ‘POST’,
‘callback’ => ‘mk_symulator_report_handler’,
‘permission_callback’ => ‘__return_true’,
]);
});

// — WooCommerce: inicjalizacja licznika sesji po oplacie —
add_action(‘woocommerce_order_status_completed’, ‘mk_sym_init_sessions’);
add_action(‘woocommerce_order_status_processing’, ‘mk_sym_init_sessions’);

function mk_sym_init_sessions($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;

$sessions = mk_sym_order_sessions($order);
if ($sessions <= 0) return; $order_key = $order->get_order_key();
$opt_key = ‘mk_sym_sess_count_’ . $order_key;

// Inicjalizuj tylko raz (nie nadpisuj jesli juz ustawiono)
if (get_option($opt_key) === false) {
update_option($opt_key, $sessions, false);
}

// Fakturownia – wystaw fakture
if (MK_FAKTUROWNIA_TOKEN && MK_FAKTUROWNIA_DOMAIN) {
mk_sym_create_fakturownia_invoice($order);
}
}

function mk_sym_order_has_product($order) {
if (empty(mk_sym_products())) return true; // brak filtru produktu = wszystkie zamowienia
return mk_sym_order_sessions($order) > 0;
}

// — WooCommerce: redirect na symulator po zakupie —
add_action(‘woocommerce_thankyou’, ‘mk_sym_redirect_after_purchase’);

function mk_sym_redirect_after_purchase($order_id) {
if (MK_SYM_PRODUCT_ID <= 0) return; $order = wc_get_order($order_id); if (!$order || !mk_sym_order_has_product($order)) return; $order_key = $order->get_order_key();
$redirect_url = home_url(‘/symulator-rozmowy/?token=’ . urlencode($order_key));
?>

🎉

Dziękujemy za zakup!

Za chwilę zostaniesz przekierowany do Symulatora…

Przejdź do Symulatora →


function mk_symulator_start_handler(WP_REST_Request $request) {
$order_key = sanitize_text_field($request->get_param(‘order_key’) ?? ”);
$dev_secret = sanitize_text_field($request->get_param(‘dev_secret’) ?? ”);

$is_dev = defined(‘MK_SYM_DEV_SECRET’) && $dev_secret === MK_SYM_DEV_SECRET;

if ($is_dev) {
$token = wp_generate_uuid4();
set_transient(‘mk_sym_token_’ . $token, [‘dev’ => true, ‘calls’ => 0], 6 * HOUR_IN_SECONDS);
return rest_ensure_response([‘session_token’ => $token, ‘sessions_left’ => 99]);
}

if (empty($order_key)) {
return new WP_Error(‘no_key’, “Brak klucza zam\u{f3}wienia.”, [‘status’ => 400]);
}

// Walidacja zamowienia WooCommerce
$order_id = wc_get_order_id_by_order_key($order_key);
if (!$order_id) {
return new WP_Error(‘invalid_order’, “Nieprawid\u{142}owy klucz zam\u{f3}wienia.”, [‘status’ => 403]);
}

$order = wc_get_order($order_id);
$status = $order->get_status();
if (!in_array($status, [‘completed’, ‘processing’], true)) {
return new WP_Error(‘unpaid’, “Zam\u{f3}wienie nie zosta\u{142}o op\u{142}acone.”, [‘status’ => 402]);
}

// Licznik sesji
$opt_key = ‘mk_sym_sess_count_’ . $order_key;
$remaining = (int) get_option($opt_key, -1);

if ($remaining < 0) { // Zamowienie oplacone ale inicjalizacja nie nastapila (edge case) – zainicjuj teraz $sessions = mk_sym_order_sessions($order); if ($sessions > 0) {
update_option($opt_key, $sessions – 1, false);
$remaining = $sessions – 1;
} else {
return new WP_Error(‘no_product’, “Zam\u{f3}wienie nie zawiera Symulatora.”, [‘status’ => 403]);
}
} elseif ($remaining <= 0) { return new WP_Error(‘no_sessions’, “Wszystkie sesje zosta\u{142}y wykorzystane. Kup nowy dost\u{119}p.”, [‘status’ => 403]);
} else {
update_option($opt_key, $remaining – 1, false);
$remaining = $remaining – 1;
}

$token = wp_generate_uuid4();
set_transient(‘mk_sym_token_’ . $token, [‘order_key’ => $order_key, ‘calls’ => 0], 6 * HOUR_IN_SECONDS);

return rest_ensure_response([‘session_token’ => $token, ‘sessions_left’ => $remaining]);
}

// — Endpoint: wywolanie Claude —
function mk_symulator_handler(WP_REST_Request $request) {
@set_time_limit(120);

$messages = $request->get_param(‘messages’);
$system = $request->get_param(‘system’) ?? ”;
$max_tokens = (int) ($request->get_param(‘max_tokens’) ?? 600);
$dev_secret = sanitize_text_field($request->get_param(‘dev_secret’) ?? ”);
$session_token = sanitize_text_field($request->get_param(‘session_token’) ?? ”);

$is_dev = defined(‘MK_SYM_DEV_SECRET’) && $dev_secret === MK_SYM_DEV_SECRET;
$is_valid_session = false;

if (!$is_dev && !empty($session_token)) {
$sess = get_transient(‘mk_sym_token_’ . $session_token);
if ($sess !== false) {
$is_valid_session = true;
$sess[‘calls’] = ($sess[‘calls’] ?? 0) + 1;
set_transient(‘mk_sym_token_’ . $session_token, $sess, 6 * HOUR_IN_SECONDS);
}
}

if (!is_array($messages) || empty($messages)) {
return new WP_Error(‘invalid’, “Brak wiadomo\u{15b}ci.”, [‘status’ => 400]);
}

if (!$is_dev && !$is_valid_session) {
return new WP_Error(‘unauthorized’, “Brak aktywnej sesji. Kup dost\u{119}p, aby korzysta\u{107} z Symulatora.”, [‘status’ => 401]);
}

$api_key = defined(‘MK_ANTHROPIC_KEY’) ? MK_ANTHROPIC_KEY : ”;
if (!$api_key) {
return new WP_Error(‘no_key’, ‘Brak konfiguracji API.’, [‘status’ => 500]);
}

$model = ($max_tokens >= 2000) ? ‘claude-sonnet-4-6’ : ‘claude-haiku-4-5-20251001’;
$max_tokens = min($max_tokens, 4000);

$body = [
‘model’ => $model,
‘max_tokens’ => $max_tokens,
‘messages’ => $messages,
];
if (!empty($system)) $body[‘system’] = (string) $system;

$anthropic_response = wp_remote_post(‘https://api.anthropic.com/v1/messages’, [
‘timeout’ => 90,
‘headers’ => [
‘Content-Type’ => ‘application/json’,
‘x-api-key’ => $api_key,
‘anthropic-version’ => ‘2023-06-01’,
],
‘body’ => wp_json_encode($body),
]);

if (is_wp_error($anthropic_response)) {
return new WP_Error(‘api_error’, “B\u{142}\u{105}d po\u{142}\u{105}czenia z API.”, [‘status’ => 502]);
}

$data = json_decode(wp_remote_retrieve_body($anthropic_response), true);
$code = wp_remote_retrieve_response_code($anthropic_response);

if ($code !== 200) {
$msg = $data[‘error’][‘message’] ?? “B\u{142}\u{105}d API Anthropic.”;
return new WP_Error(‘api_error’, $msg, [‘status’ => 502]);
}

$text = $data[‘content’][0][‘text’] ?? ”;
return rest_ensure_response([‘text’ => $text]);
}

// — Endpoint: wyslij raport emailem po sesji —
function mk_symulator_report_handler(WP_REST_Request $request) {
$session_token = sanitize_text_field($request->get_param(‘session_token’) ?? ”);
$dev_secret = sanitize_text_field($request->get_param(‘dev_secret’) ?? ”);
$user_email = sanitize_email($request->get_param(‘user_email’) ?? ”);
$eval_data = $request->get_param(‘eval_data’);
$role = sanitize_text_field($request->get_param(‘role’) ?? ”);
$level = sanitize_text_field($request->get_param(‘level’) ?? ”);

$is_dev = defined(‘MK_SYM_DEV_SECRET’) && $dev_secret === MK_SYM_DEV_SECRET;

if (!$is_dev && !empty($session_token)) {
$sess = get_transient(‘mk_sym_token_’ . $session_token);
if ($sess === false) {
return new WP_Error(‘unauthorized’, ‘Nieaktywna sesja.’, [‘status’ => 401]);
}
} elseif (!$is_dev) {
return new WP_Error(‘unauthorized’, ‘Brak tokenu sesji.’, [‘status’ => 401]);
}

if (!is_email($user_email)) {
return new WP_Error(‘no_email’, ‘Brak adresu email.’, [‘status’ => 400]);
}

if (!is_array($eval_data) || empty($eval_data[‘overall_score’])) {
return new WP_Error(‘no_eval’, ‘Brak danych ewaluacji.’, [‘status’ => 400]);
}

$sent = mk_sym_send_report_email($user_email, $eval_data, $role, $level);

if (!$sent) {
return new WP_Error(‘mail_error’, ‘Nie udalo sie wyslac emaila.’, [‘status’ => 500]);
}

return rest_ensure_response([‘ok’ => true]);
}

function mk_sym_send_report_email($email, $eval, $role, $level) {
$score = (int) ($eval[‘overall_score’] ?? 0);
$title = esc_html($eval[‘overall_title’] ?? ”);
$summary = esc_html($eval[‘overall_summary’] ?? ”);
$role_label = esc_html($role ?: ‘rekrutacja’);
$level_label = esc_html($level ?: ”);

$score_color = $score >= 80 ? ‘#10b981’ : ($score >= 60 ? ‘#f59e0b’ : ‘#ef4444’);

// Kategorie
$cats_html = ”;
foreach ((array) ($eval[‘categories’] ?? []) as $cat) {
$s = (int) ($cat[‘score’] ?? 0);
$c = $s >= 80 ? ‘#10b981’ : ($s >= 60 ? ‘#f59e0b’ : ‘#ef4444’);
$w = round($s * 0.9);
$cats_html .= ‘

‘ . esc_html($cat[‘name’] ?? ”) . ‘


. ‘


. ‘

‘ . $s . ‘

‘;
}

// Q&A feedback
$qa_html = ”;
foreach ((array) ($eval[‘qa_feedback’] ?? []) as $i => $qa) {
$q_score = (int) ($qa[‘score’] ?? 0);
$q_color = $q_score >= 80 ? ‘#10b981’ : ($q_score >= 60 ? ‘#f59e0b’ : ‘#ef4444’);
$qa_html .= ‘


. ‘

Pytanie ‘ . ($i + 1) . ‘ • Ocena: ‘ . $q_score . ‘/100


. ‘

‘ . esc_html($qa[‘question’] ?? ”) . ‘


. ‘

‘ . esc_html($qa[‘feedback’] ?? ”) . ‘


. ‘

💡 Tip: ‘ . esc_html($qa[‘tip’] ?? ”) . ‘


. ‘

‘;
}

// Mocne / do wzmocnienia
$mocne_html = ”;
foreach ((array) ($eval[‘mocne’] ?? []) as $m) {
if ($m) $mocne_html .= ‘

  • ‘ . esc_html($m) . ‘

‘;
}
$slabe_html = ”;
foreach ((array) ($eval[‘do_wzmocnienia’] ?? []) as $s) {
if ($s) $slabe_html .= ‘

  • ‘ . esc_html($s) . ‘

‘;
}

$html = <<<HTML

MANAGER KARIERY
Twój raport z rozmowy rekrutacyjnej
Stanowisko: {$role_label} • Poziom: {$level_label}
{$score}
/ 100 PKT
{$title}
{$summary}
Ocena kategorii

{$cats_html}

Mocne strony
    {$mocne_html}
Do wzmocnienia
    {$slabe_html}
Feedback per pytanie

{$qa_html}

Chcesz jeszcze popracowac nad odpowiedziami? Wykup kolejne sesje i cwicz rozne role.

Kup kolejne sesje →

Manager Kariery • managerkariery.com
Maciej Czerwonka • 14 lat w HR • 900+ konsultacji

HTML;

$subject = ‘Twoj raport z rozmowy rekrutacyjnej (‘ . $role_label . ‘) – ‘ . $score . ‘/100 pkt’;
$headers = [
‘Content-Type: text/html; charset=UTF-8’,
‘From: Manager Kariery <noreply@managerkariery.com>’,
];

return wp_mail($email, $subject, $html, $headers);
}

// — Fakturownia: wystaw fakture po zakupie —
function mk_sym_create_fakturownia_invoice($order) {
$token = MK_FAKTUROWNIA_TOKEN;
$domain = MK_FAKTUROWNIA_DOMAIN;
if (!$token || !$domain) return;

$total = number_format((float) $order->get_total(), 2, ‘.’, ”);
$net = number_format((float) $order->get_total() / 1.23, 2, ‘.’, ”); // VAT 23%
$vat_val = number_format((float) $order->get_total() – (float) $net, 2, ‘.’, ”);
$name = trim($order->get_billing_first_name() . ‘ ‘ . $order->get_billing_last_name());
$email = $order->get_billing_email();
$order_id = $order->get_id();

// Nazwa pozycji z realnych produktow zamowienia (nie hardcode “3 sesje”)
$item_names = [];
foreach ($order->get_items() as $item) {
$item_names[] = $item->get_name();
}
$position_name = $item_names ? implode(‘ + ‘, $item_names) : ‘Symulator Rozmowy Rekrutacyjnej’;

$invoice_data = [
‘api_token’ => $token,
‘invoice’ => [
‘kind’ => ‘vat’,
‘number’ => null,
‘sell_date’ => date(‘Y-m-d’),
‘issue_date’ => date(‘Y-m-d’),
‘payment_to’ => date(‘Y-m-d’),
‘payment_type’ => ‘transfer’,
‘status’ => ‘issued’,
‘buyer_name’ => $name,
‘buyer_email’ => $email,
‘buyer_city’ => $order->get_billing_city(),
‘buyer_post_code’ => $order->get_billing_postcode(),
‘buyer_street’ => $order->get_billing_address_1(),
‘buyer_country’ => $order->get_billing_country(),
‘description’ => ‘Zamowienie WooCommerce #’ . $order_id,
‘positions’ => [[
‘name’ => $position_name,
‘quantity’ => 1,
‘quantity_unit’ => ‘szt’,
‘tax’ => 23,
‘price_net’ => $net,
‘price_gross’ => $total,
‘total_price_gross’ => $total,
‘total_price_net’ => $net,
]],
],
];

// Nie wystawiaj drugiej faktury dla tego samego zamowienia
if ($order->get_meta(‘_mk_fakturownia_invoice_id’)) return;

$resp = wp_remote_post(‘https://’ . $domain . ‘.fakturownia.pl/invoices.json’, [
‘timeout’ => 30,
‘headers’ => [‘Content-Type’ => ‘application/json’],
‘body’ => wp_json_encode($invoice_data),
]);

// Zapisz wynik zamiast wysylac w ciemno (dotad bledy 401 znikaly po cichu)
if (is_wp_error($resp)) {
$order->add_order_note(‘Fakturownia: blad polaczenia – ‘ . $resp->get_error_message());
error_log(‘MK Fakturownia WP_Error: ‘ . $resp->get_error_message());
return;
}
$code = wp_remote_retrieve_response_code($resp);
$body = json_decode(wp_remote_retrieve_body($resp), true);
if ($code >= 200 && $code < 300 && !empty($body[‘id’])) { $invoice_id = $body[‘id’]; $order->update_meta_data(‘_mk_fakturownia_invoice_id’, $invoice_id);
$order->update_meta_data(‘_mk_fakturownia_invoice_number’, $body[‘number’] ?? ”);
$order->save();
$order->add_order_note(‘Fakturownia: faktura ‘ . ($body[‘number’] ?? $invoice_id) . ‘ wystawiona.’);

// Wyslij fakture emailem przez osobny endpoint (send_by_email w payload = 422)
wp_remote_post(‘https://’ . $domain . ‘.fakturownia.pl/invoices/’ . $invoice_id . ‘/send_by_email.json?api_token=’ . $token, [
‘timeout’ => 20,
‘headers’ => [‘Content-Type’ => ‘application/json’],
‘body’ => ‘{}’,
]);
} else {
$msg = is_array($body) ? wp_json_encode($body) : wp_remote_retrieve_body($resp);
$order->add_order_note(‘Fakturownia: BLAD ‘ . $code . ‘ – ‘ . mb_substr((string) $msg, 0, 300));
error_log(‘MK Fakturownia HTTP ‘ . $code . ‘: ‘ . (string) $msg);
}
}

Menu