feat(127): polkurier integration foundation

Single-instance globalna konfiguracja polkurier.pl jako alternatywa
dla Apaczki: szyfrowany login + Token API, karta w hubie integracji
i realny test polaczenia przez apimetod=test_auth_api zweryfikowany
na zywym koncie operatora (Autoryzacja: 1).

ShipmentProviderRegistry netkniety - PolkurierShipmentService/
TrackingService w kolejnych fazach.

Kluczowe ustalenia kontraktu API (z SDK polkurier-sdk):
- POST https://api.polkurier.pl/ (jeden endpoint)
- JSON body: {authorization:{login,token}, apimetod, data}
- Sukces: top-level status === 'success' (nie 'ok')
- Blad: tresc w polu 'response' envelope'a
- Content-Type: application/json (strict, bez charset suffix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 11:43:11 +02:00
parent 541e61bf7d
commit 3443879f59
17 changed files with 1391 additions and 17 deletions

View File

@@ -586,6 +586,7 @@ return [
'shoppro' => 'shopPRO',
'hostedsms' => 'HostedSMS',
'smsplanet' => 'SMSPLANET',
'polkurier' => 'polkurier.pl',
'shoppro_instances' => ':count instancji',
],
'status' => [
@@ -741,6 +742,47 @@ return [
'test_failed' => 'Nie udalo sie polaczyc z API Apaczka.',
],
],
'polkurier' => [
'title' => 'Integracja polkurier.pl',
'description' => 'Broker kurierski polkurier.pl - alternatywa dla Apaczki. Token API generujesz w Panel Klienta > Ustawienia > Token API.',
'config' => [
'title' => 'Konfiguracja API',
],
'test' => [
'title' => 'Test polaczenia',
'description' => 'Test realnie wywoluje metode test_auth_api w API polkurier (bez tworzenia przesylki).',
],
'fields' => [
'login' => 'Login (e-mail z panelu klienta)',
'api_token' => 'Token API',
'default_label_format' => 'Domyslny format etykiety',
'is_active' => 'Integracja aktywna',
],
'hints' => [
'login' => 'Login uzywany przy generowaniu Token API w Panel Klienta polkurier.',
],
'token' => [
'saved' => 'Token API jest zapisany. Pozostaw pole puste, aby nie zmieniac.',
'missing' => 'Brak zapisanego Token API.',
],
'status' => [
'token' => 'Token API',
'active' => 'Aktywna',
'saved' => 'zapisany',
'missing' => 'brak',
'last_test' => 'Ostatni test',
],
'actions' => [
'save' => 'Zapisz ustawienia polkurier',
'send_test' => 'Testuj polaczenie',
],
'flash' => [
'saved' => 'Ustawienia polkurier zostaly zapisane.',
'save_failed' => 'Nie udalo sie zapisac ustawien polkurier.',
'test_success' => 'Polaczenie z polkurier dziala. :message',
'test_failed' => 'Nie udalo sie polaczyc z API polkurier.',
],
],
'hostedsms' => [
'title' => 'Integracja HostedSMS',
'description' => 'Konfiguracja konta HostedSMS do wysylki SMS z orderPRO.',

View File

@@ -0,0 +1,110 @@
<?php
$settings = is_array($settings ?? null) ? $settings : [];
$login = trim((string) ($settings['login'] ?? ''));
$labelFormat = strtoupper(trim((string) ($settings['default_label_format'] ?? 'PDF'))) ?: 'PDF';
$hasToken = (bool) ($settings['has_api_token'] ?? false);
$isActive = (bool) ($settings['is_active'] ?? true);
$lastTestAt = trim((string) ($settings['last_test_at'] ?? ''));
$lastTestStatus = trim((string) ($settings['last_test_status'] ?? ''));
$lastTestMessage = trim((string) ($settings['last_test_message'] ?? ''));
$lastTestHttpCode = $settings['last_test_http_code'] ?? null;
$labelFormats = ['PDF', 'ZPL', 'EPL'];
?>
<section class="card">
<h2 class="section-title"><?= $e($t('settings.polkurier.title')) ?></h2>
<p class="muted mt-12"><?= $e($t('settings.polkurier.description')) ?></p>
<?php if (!empty($errorMessage)): ?>
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
<?php endif; ?>
<?php if (!empty($successMessage)): ?>
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
<?php endif; ?>
<?php if (!empty($testMessage)): ?>
<div class="mt-12"><?php $type='info'; $message=(string) $testMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
<?php endif; ?>
</section>
<section class="card mt-16">
<h3 class="section-title"><?= $e($t('settings.polkurier.config.title')) ?></h3>
<div class="muted mt-12">
<?= $e($t('settings.polkurier.status.token')) ?>:
<strong><?= $e($hasToken ? $t('settings.polkurier.status.saved') : $t('settings.polkurier.status.missing')) ?></strong>
|
<?= $e($t('settings.polkurier.status.active')) ?>:
<strong><?= $e($isActive ? $t('settings.integrations_hub.active.yes') : $t('settings.integrations_hub.active.no')) ?></strong>
</div>
<form class="statuses-form mt-16" action="/settings/integrations/polkurier/save" method="post" novalidate>
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.polkurier.fields.login')) ?></span>
<input class="form-control" type="text" name="login" maxlength="190" value="<?= $e($login) ?>" required>
<span class="muted"><?= $e($t('settings.polkurier.hints.login')) ?></span>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.polkurier.fields.api_token')) ?></span>
<input class="form-control" type="password" name="api_token" autocomplete="new-password" placeholder="<?= $hasToken ? '********' : '' ?>" <?= $hasToken ? '' : 'required' ?>>
<span class="muted"><?= $e($hasToken ? $t('settings.polkurier.token.saved') : $t('settings.polkurier.token.missing')) ?></span>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.polkurier.fields.default_label_format')) ?></span>
<select class="form-control" name="default_label_format">
<?php foreach ($labelFormats as $fmt): ?>
<option value="<?= $e($fmt) ?>"<?= $fmt === $labelFormat ? ' selected' : '' ?>><?= $e($fmt) ?></option>
<?php endforeach; ?>
</select>
</label>
<label class="form-field form-field--inline">
<input type="checkbox" name="is_active" value="1"<?= $isActive ? ' checked' : '' ?>>
<span class="field-label"><?= $e($t('settings.polkurier.fields.is_active')) ?></span>
</label>
<div class="form-actions mt-16">
<button type="submit" class="btn btn--primary"><?= $e($t('settings.polkurier.actions.save')) ?></button>
</div>
</form>
</section>
<section class="card mt-16">
<h3 class="section-title"><?= $e($t('settings.polkurier.test.title')) ?></h3>
<p class="muted mt-12"><?= $e($t('settings.polkurier.test.description')) ?></p>
<form class="statuses-form mt-16" action="/settings/integrations/polkurier/test" method="post" novalidate>
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<div class="form-actions mt-16">
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.polkurier.actions.send_test')) ?></button>
</div>
</form>
<?php if ($lastTestAt !== ''): ?>
<div class="mt-16">
<?php
$type = $lastTestStatus === 'ok' ? 'success' : 'danger';
$parts = [];
$parts[] = $e($t('settings.polkurier.status.last_test')) . ': ' . $e($lastTestAt);
if ($lastTestStatus !== '') {
$parts[] = '<span class="badge badge--' . ($lastTestStatus === 'ok' ? 'success' : 'muted') . '">' . $e(strtoupper($lastTestStatus)) . '</span>';
}
if ($lastTestHttpCode !== null) {
$parts[] = '<span class="badge badge--muted">HTTP ' . $e((string) $lastTestHttpCode) . '</span>';
}
if ($lastTestMessage !== '') {
$parts[] = $e($lastTestMessage);
}
$messageHtml = implode(' &middot; ', $parts);
$dismissible = false;
include dirname(__DIR__) . '/components/alert.php';
unset($messageHtml);
?>
</div>
<?php endif; ?>
</section>