From 137b15c97bb306b9e32d2a0774b5f81a1df1e3ea Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Sat, 14 Feb 2026 20:27:01 +0100 Subject: [PATCH] ver. 0.270 - Apilo payment/status sync hardening --- autoload/shop/class.Order.php | 290 +++++++++++++++++++++++++------ cron.php | 1 + docs/CHANGELOG.md | 12 ++ docs/PROJECT_STRUCTURE.md | 7 + docs/TESTING.md | 10 ++ updates/0.20/ver_0.270.zip | Bin 0 -> 10728 bytes updates/0.20/ver_0.270_files.txt | 0 updates/changelog.php | 9 +- updates/versions.php | 2 +- 9 files changed, 280 insertions(+), 51 deletions(-) create mode 100644 updates/0.20/ver_0.270.zip create mode 100644 updates/0.20/ver_0.270_files.txt diff --git a/autoload/shop/class.Order.php b/autoload/shop/class.Order.php index 23b61a9..251f4be 100644 --- a/autoload/shop/class.Order.php +++ b/autoload/shop/class.Order.php @@ -3,6 +3,8 @@ namespace shop; class Order implements \ArrayAccess { + private const APILO_SYNC_QUEUE_FILE = '/temp/apilo-sync-queue.json'; + public $id; public $products; public $statuses; @@ -89,31 +91,9 @@ class Order implements \ArrayAccess file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND ); } - if ( $this -> apilo_order_id ) + if ( $this -> apilo_order_id and !$this -> sync_apilo_payment() ) { - $payment_date = new \DateTime( $this -> date_order ); - $access_token = \admin\factory\Integrations::apilo_get_access_token(); - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/payment/' ); - curl_setopt( $ch, CURLOPT_POST, 1 ); - curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [ - 'amount' => str_replace( ',', '.', $this -> summary ), - 'paymentDate' => $payment_date -> format('Y-m-d\TH:i:s\Z'), - 'type' => 1 - ] ) ); - curl_setopt( $ch, CURLOPT_HTTPHEADER, [ - "Authorization: Bearer " . $access_token, - "Accept: application/json" - ] ); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true); - $apilo_response = curl_exec( $ch ); - - // put response to log - if ( $config['debug']['apilo'] ) - file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $apilo_response, true ) . "\n\n", FILE_APPEND ); - - curl_close( $ch ); + self::queue_apilo_sync( (int)$this -> id, true, null, 'payment_sync_failed' ); } } @@ -167,31 +147,9 @@ class Order implements \ArrayAccess file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND ); } - if ( $this -> apilo_order_id ) + if ( $this -> apilo_order_id and !$this -> sync_apilo_status( (int)$status ) ) { - $access_token = \admin\factory\Integrations::apilo_get_access_token(); - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/status/' ); - curl_setopt( $ch, CURLOPT_POST, 1 ); - curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT"); - curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [ - 'id' => $this -> apilo_order_id, - 'status' => (int)\front\factory\ShopStatuses::get_apilo_status_id( $status ) - ] ) ); - curl_setopt( $ch, CURLOPT_HTTPHEADER, [ - "Authorization: Bearer " . $access_token, - "Accept: application/json", - "Content-Type: application/json" - ] ); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true); - $apilo_result = curl_exec( $ch ); - - // put response to log - if ( $config['debug']['apilo'] ) - file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $apilo_result, true ) . "\n\n", FILE_APPEND ); - - curl_close( $ch ); + self::queue_apilo_sync( (int)$this -> id, false, (int)$status, 'status_sync_failed' ); } } @@ -361,4 +319,238 @@ class Order implements \ArrayAccess { unset( $this -> $offset ); } -} \ No newline at end of file + + private function sync_apilo_payment(): bool + { + global $config; + + if ( !(int)$this -> apilo_order_id ) + return true; + + $payment_type = (int)\front\factory\ShopPaymentMethod::get_apilo_payment_method_id( (int)$this -> payment_method_id ); + if ( $payment_type <= 0 ) + $payment_type = 1; + + $payment_date = new \DateTime( $this -> date_order ); + $access_token = \admin\factory\Integrations::apilo_get_access_token(); + + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/payment/' ); + curl_setopt( $ch, CURLOPT_POST, 1 ); + curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [ + 'amount' => str_replace( ',', '.', $this -> summary ), + 'paymentDate' => $payment_date -> format('Y-m-d\TH:i:s\Z'), + 'type' => $payment_type + ] ) ); + curl_setopt( $ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $access_token, + "Accept: application/json", + "Content-Type: application/json" + ] ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); + curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 ); + curl_setopt( $ch, CURLOPT_TIMEOUT, 15 ); + $apilo_response = curl_exec( $ch ); + $http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE ); + $curl_error = curl_errno( $ch ) ? curl_error( $ch ) : ''; + curl_close( $ch ); + + if ( $config['debug']['apilo'] ) + { + self::append_apilo_log( "PAYMENT RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_response, true ) . "\n" ); + } + + if ( $curl_error !== '' ) + return false; + + if ( $http_code < 200 or $http_code >= 300 ) + return false; + + return true; + } + + private function sync_apilo_status( int $status ): bool + { + global $config; + + if ( !(int)$this -> apilo_order_id ) + return true; + + $access_token = \admin\factory\Integrations::apilo_get_access_token(); + + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/status/' ); + curl_setopt( $ch, CURLOPT_POST, 1 ); + curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [ + 'id' => $this -> apilo_order_id, + 'status' => (int)\front\factory\ShopStatuses::get_apilo_status_id( $status ) + ] ) ); + curl_setopt( $ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $access_token, + "Accept: application/json", + "Content-Type: application/json" + ] ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); + curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 ); + curl_setopt( $ch, CURLOPT_TIMEOUT, 15 ); + $apilo_result = curl_exec( $ch ); + $http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE ); + $curl_error = curl_errno( $ch ) ? curl_error( $ch ) : ''; + curl_close( $ch ); + + if ( $config['debug']['apilo'] ) + { + self::append_apilo_log( "STATUS RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_result, true ) . "\n" ); + } + + if ( $curl_error !== '' ) + return false; + + if ( $http_code < 200 or $http_code >= 300 ) + return false; + + return true; + } + + public static function process_apilo_sync_queue( int $limit = 10 ): int + { + $queue = self::load_apilo_sync_queue(); + if ( !\S::is_array_fix( $queue ) ) + return 0; + + $processed = 0; + + foreach ( $queue as $key => $task ) + { + if ( $processed >= $limit ) + break; + + $order_id = (int)( $task['order_id'] ?? 0 ); + if ( $order_id <= 0 ) + { + unset( $queue[$key] ); + continue; + } + + $order = new self( $order_id ); + if ( !(int)$order -> id ) + { + unset( $queue[$key] ); + continue; + } + + $error = ''; + $sync_failed = false; + + $payment_pending = !empty( $task['payment'] ) and (int)$order -> paid === 1; + if ( $payment_pending and (int)$order -> apilo_order_id ) + { + if ( !$order -> sync_apilo_payment() ) + { + $sync_failed = true; + $error = 'payment_sync_failed'; + } + } + + $status_pending = isset( $task['status'] ) and $task['status'] !== null and $task['status'] !== ''; + if ( !$sync_failed and $status_pending and (int)$order -> apilo_order_id ) + { + if ( !$order -> sync_apilo_status( (int)$task['status'] ) ) + { + $sync_failed = true; + $error = 'status_sync_failed'; + } + } + + if ( $sync_failed ) + { + $task['attempts'] = (int)( $task['attempts'] ?? 0 ) + 1; + $task['last_error'] = $error; + $task['updated_at'] = date( 'Y-m-d H:i:s' ); + $queue[$key] = $task; + } + else + { + unset( $queue[$key] ); + } + + $processed++; + } + + self::save_apilo_sync_queue( $queue ); + + return $processed; + } + + private static function queue_apilo_sync( int $order_id, bool $payment, ?int $status, string $error ): void + { + if ( $order_id <= 0 ) + return; + + $queue = self::load_apilo_sync_queue(); + $key = (string)$order_id; + $row = is_array( $queue[$key] ?? null ) ? $queue[$key] : []; + + $row['order_id'] = $order_id; + $row['payment'] = !empty( $row['payment'] ) || $payment ? 1 : 0; + if ( $status !== null ) + $row['status'] = $status; + + $row['attempts'] = (int)( $row['attempts'] ?? 0 ) + 1; + $row['last_error'] = $error; + $row['updated_at'] = date( 'Y-m-d H:i:s' ); + + $queue[$key] = $row; + self::save_apilo_sync_queue( $queue ); + } + + private static function apilo_sync_queue_path(): string + { + return dirname( __DIR__, 2 ) . self::APILO_SYNC_QUEUE_FILE; + } + + private static function load_apilo_sync_queue(): array + { + $path = self::apilo_sync_queue_path(); + if ( !file_exists( $path ) ) + return []; + + $content = file_get_contents( $path ); + if ( !$content ) + return []; + + $decoded = json_decode( $content, true ); + if ( !is_array( $decoded ) ) + return []; + + return $decoded; + } + + private static function save_apilo_sync_queue( array $queue ): void + { + $path = self::apilo_sync_queue_path(); + $dir = dirname( $path ); + if ( !is_dir( $dir ) ) + mkdir( $dir, 0777, true ); + + file_put_contents( $path, json_encode( $queue, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), LOCK_EX ); + } + + private static function append_apilo_log( string $message ): void + { + $base = isset( $_SERVER['DOCUMENT_ROOT'] ) && $_SERVER['DOCUMENT_ROOT'] + ? rtrim( $_SERVER['DOCUMENT_ROOT'], '/\\' ) + : dirname( __DIR__, 2 ); + + $dir = $base . '/logs'; + if ( !is_dir( $dir ) ) + mkdir( $dir, 0777, true ); + + file_put_contents( + $dir . '/apilo.txt', + date( 'Y-m-d H:i:s' ) . ' --- ' . $message . "\n\n", + FILE_APPEND + ); + } +} diff --git a/cron.php b/cron.php index f8996a9..921d385 100644 --- a/cron.php +++ b/cron.php @@ -59,6 +59,7 @@ $apilo_settings = \admin\factory\Integrations::apilo_settings(); if ( (int)($apilo_settings['enabled'] ?? 0) === 1 ) { \admin\factory\Integrations::apilo_keepalive( 300 ); $apilo_settings = \admin\factory\Integrations::apilo_settings(); + Order::process_apilo_sync_queue( 10 ); } function parsePaczkomatAddress($input) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 657e55b..c31dc33 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,18 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- +## ver. 0.270 (2026-02-14) - Apilo payment/status sync hardening + +- **Shop/Order + Apilo** - utwardzenie synchronizacji platnosci i statusow zamowien + - FIX: `shop\Order::set_as_paid()` wysyla do Apilo mapowany typ platnosci (`payment_method_id` -> `apilo_payment_type_id`) zamiast stalego `type = 1` + - NOWE: retry queue dla chwilowej niedostepnosci Apilo (`temp/apilo-sync-queue.json`) dla sync platnosci i statusu + - NOWE: `shop\Order::process_apilo_sync_queue()` przetwarza zalegle syncy + - UPDATE: `cron.php` uruchamia przetwarzanie kolejki sync Apilo przy aktywnej integracji + - UPDATE: rozszerzone logowanie debug (`logs/apilo.txt`) o HTTP code i bledy cURL dla sync platnosci/statusu +- Testy: **OK (300 tests, 895 assertions)** + +--- + ## ver. 0.269 (2026-02-14) - ShopTransport - **ShopTransport** - migracja `/admin/shop_transport` na Domain + DI + nowe widoki diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index 016afee..accb166 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -62,6 +62,8 @@ shop\product:{product_id}:{lang_id}:{permutation_hash} - Czestotliwosc: Co 10 minut - **Synchronizacja cennika:** masowa aktualizacja cen z Apilo - Czestotliwosc: Co 1 godzine +- **Synchronizacja zaleglych syncow platnosci/statusow:** kolejka retry dla chwilowej niedostepnosci Apilo (`temp/apilo-sync-queue.json`) + - Przetwarzanie: przy kazdym uruchomieniu `cron.php` (limit wsadowy) **Uwaga:** Integracje Sellasist i Baselinker zostaly usuniete w ver. 0.263. @@ -242,6 +244,11 @@ autoload/ - Usunieto legacy: `autoload/admin/controls/class.ShopTransport.php`, `autoload/admin/view/class.ShopTransport.php`, `admin/templates/shop-transport/view-list.php`. - `admin\factory\ShopTransport` i `front\factory\ShopTransport` przepiete na repozytorium. +**Aktualizacja 2026-02-14 (ver. 0.270):** +- `shop\Order` zapisuje nieudane syncy Apilo (status/platnosc) do kolejki `temp/apilo-sync-queue.json`. +- `cron.php` automatycznie ponawia zalegle syncy (`Order::process_apilo_sync_queue()`). +- `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`. + ### Routing admin (admin\Site::route()) 1. Sprawdź mapę `$newControllers` → utwórz instancję z DI → wywołaj 2. Jeśli nowy kontroler nie istnieje (`class_exists()` = false) → fallback na `admin\controls\` diff --git a/docs/TESTING.md b/docs/TESTING.md index 18f4481..e1e4026 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -375,3 +375,13 @@ OK (300 tests, 895 assertions) Nowe testy dodane 2026-02-14: - `tests/Unit/Domain/Transport/TransportRepositoryTest.php` (14 testow: find invalid/null/normalize/nullables, save insert/update/failure/default reset/switch normalization, listForAdmin whitelist, allActive, getApiloCarrierAccountId, getTransportCost, allForAdmin) - `tests/Unit/admin/Controllers/ShopTransportControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora z 2 repo) + +## Aktualizacja suite (Apilo sync hardening, ver. 0.270) +Ostatnio zweryfikowano: 2026-02-14 + +```text +OK (300 tests, 895 assertions) +``` + +Zmiany testowe 2026-02-14: +- brak nowych testow; pelna regresja po zmianach sync Apilo (TPAY -> Apilo) przeszla bez bledow diff --git a/updates/0.20/ver_0.270.zip b/updates/0.20/ver_0.270.zip new file mode 100644 index 0000000000000000000000000000000000000000..548049692b7973ce9999b384f1f4ae1eb5daec9d GIT binary patch literal 10728 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 T0Qo-~u>UgXzp6s>Ki&TYJ7t<$ literal 0 HcmV?d00001 diff --git a/updates/0.20/ver_0.270_files.txt b/updates/0.20/ver_0.270_files.txt new file mode 100644 index 0000000..e69de29 diff --git a/updates/changelog.php b/updates/changelog.php index f272a35..60ef483 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,3 +1,11 @@ +ver. 0.270 - 14.02.2026
+- FIX - Apilo: `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z `payment_method_id`), zamiast stalego `type = 1` +- NEW - Apilo: dodana kolejka retry `temp/apilo-sync-queue.json` dla nieudanych syncow platnosci/statusu (chwilowa niedostepnosc API) +- UPDATE - `cron.php`: automatyczne ponawianie zaleglych syncow przez `Order::process_apilo_sync_queue(10)` +- UPDATE - debug Apilo: rozszerzone logi odpowiedzi o HTTP code i bledy cURL dla sync platnosci/statusu +- UPDATE - testy: `OK (300 tests, 895 assertions)` +- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.270.zip`, `ver_0.270_files.txt` +
ver. 0.269 - 14.02.2026
- NEW - migracja modulu `ShopPaymentMethod` do architektury Domain + DI (`Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`) - UPDATE - modul `/admin/shop_payment_method/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit` (nowe widoki listy i edycji) @@ -490,4 +498,3 @@
ver. 0.142
- FIX - poprawa adresu strony głównej - diff --git a/updates/versions.php b/updates/versions.php index 7ff553d..c23a72b 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@