PluginProbe
SuperFrete / 3.3.2
SuperFrete v3.3.2
trunk 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4
superfrete / app / Controllers / WebhookRetryManager.php

WebhookRetryManager.php in SuperFrete 3.3.2, at app/Controllers/WebhookRetryManager.php

398 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SuperFrete_API\Controllers;
4
5 use SuperFrete_API\Helpers\Logger;
6
7 if (!defined('ABSPATH')) {
8 exit; // Security check
9 }
10
11 class WebhookRetryManager
12 {
13
14 private $max_retries = 3;
15 private $retry_table;
16
17 public function __construct()
18 {
19 global $wpdb;
20 $this->retry_table = $wpdb->prefix . 'superfrete_webhook_retries';
21
22 // Hook into WordPress cron
23 add_action('superfrete_process_webhook_retries', [$this, 'process_retries']);
24
25 // Schedule cron if not already scheduled
26 if (!wp_next_scheduled('superfrete_process_webhook_retries')) {
27 wp_schedule_event(time(), 'hourly', 'superfrete_process_webhook_retries');
28 }
29 }
30
31 /**
32 * Queue a webhook for retry
33 *
34 * @param int $order_id
35 * @param string $superfrete_id
36 * @param string $event_type
37 * @param array $payload
38 * @param string $error_message
39 */
40 public function queue_retry($order_id, $superfrete_id, $event_type, $payload, $error_message)
41 {
42 global $wpdb;
43
44 // Check if this webhook is already queued for retry
45 $existing = $wpdb->get_row($wpdb->prepare(
46 "SELECT * FROM {$this->retry_table}
47 WHERE superfrete_id = %s AND event_type = %s AND status = 'pending'",
48 $superfrete_id,
49 $event_type
50 ));
51
52 if ($existing) {
53 Logger::log('SuperFrete', "Webhook retry already queued for {$superfrete_id} - {$event_type}");
54 return;
55 }
56
57 // Calculate next retry time (start with 5 minutes)
58 $next_retry = current_time('mysql', true);
59 $next_retry = date('Y-m-d H:i:s', strtotime($next_retry . ' +5 minutes'));
60
61 $result = $wpdb->insert($this->retry_table, [
62 'order_id' => $order_id,
63 'superfrete_id' => $superfrete_id,
64 'event_type' => $event_type,
65 'payload' => wp_json_encode($payload),
66 'retry_count' => 0,
67 'max_retries' => $this->max_retries,
68 'next_retry_at' => $next_retry,
69 'status' => 'pending',
70 'error_message' => $error_message,
71 'created_at' => current_time('mysql'),
72 'updated_at' => current_time('mysql')
73 ]);
74
75 if ($result) {
76 Logger::log('SuperFrete', "Webhook queued for retry: {$superfrete_id} - {$event_type}");
77 } else {
78 Logger::log('SuperFrete', "Failed to queue webhook retry: " . $wpdb->last_error);
79 }
80 }
81
82 /**
83 * Process pending retries
84 */
85 public function process_retries()
86 {
87 global $wpdb;
88
89 Logger::log('SuperFrete', 'Processing webhook retries...');
90
91 // Get pending retries that are due for processing
92 $retries = $wpdb->get_results($wpdb->prepare(
93 "SELECT * FROM {$this->retry_table}
94 WHERE status = 'pending'
95 AND next_retry_at <= %s
96 AND retry_count < max_retries
97 ORDER BY created_at ASC
98 LIMIT 10",
99 current_time('mysql', true)
100 ));
101
102 if (empty($retries)) {
103 Logger::log('SuperFrete', 'No webhook retries to process');
104 return;
105 }
106
107 Logger::log('SuperFrete', 'Found ' . count($retries) . ' webhook retries to process');
108
109 foreach ($retries as $retry) {
110 $this->process_single_retry($retry);
111 }
112
113 // Clean up old completed/failed retries
114 $this->cleanup_old_retries();
115 }
116
117 /**
118 * Process a single retry
119 *
120 * @param object $retry
121 */
122 private function process_single_retry($retry)
123 {
124 global $wpdb;
125
126 try {
127 Logger::log('SuperFrete', "Processing retry #{$retry->retry_count} for {$retry->superfrete_id}");
128
129 // Increment retry count
130 $new_retry_count = $retry->retry_count + 1;
131
132 // Try to process the webhook
133 $payload = json_decode($retry->payload, true);
134 $result = $this->retry_webhook_processing($payload, $retry);
135
136 if ($result['success']) {
137 // Mark as completed
138 $wpdb->update(
139 $this->retry_table,
140 [
141 'status' => 'completed',
142 'retry_count' => $new_retry_count,
143 'error_message' => null,
144 'updated_at' => current_time('mysql')
145 ],
146 ['id' => $retry->id],
147 ['%s', '%d', '%s', '%s'],
148 ['%d']
149 );
150
151 Logger::log('SuperFrete', "Webhook retry successful for {$retry->superfrete_id}");
152
153 } else {
154 // Check if we've reached max retries
155 if ($new_retry_count >= $retry->max_retries) {
156 // Mark as permanently failed
157 $wpdb->update(
158 $this->retry_table,
159 [
160 'status' => 'failed',
161 'retry_count' => $new_retry_count,
162 'error_message' => $result['error'],
163 'updated_at' => current_time('mysql')
164 ],
165 ['id' => $retry->id],
166 ['%s', '%d', '%s', '%s'],
167 ['%d']
168 );
169
170 Logger::log('SuperFrete', "Webhook retry permanently failed for {$retry->superfrete_id}: {$result['error']}");
171
172 } else {
173 // Schedule next retry with exponential backoff
174 $next_retry_minutes = pow(2, $new_retry_count) * 5; // 5, 10, 20 minutes
175 $next_retry = date('Y-m-d H:i:s', strtotime(current_time('mysql', true) . " +{$next_retry_minutes} minutes"));
176
177 $wpdb->update(
178 $this->retry_table,
179 [
180 'retry_count' => $new_retry_count,
181 'next_retry_at' => $next_retry,
182 'error_message' => $result['error'],
183 'updated_at' => current_time('mysql')
184 ],
185 ['id' => $retry->id],
186 ['%d', '%s', '%s', '%s'],
187 ['%d']
188 );
189
190 Logger::log('SuperFrete', "Webhook retry #{$new_retry_count} scheduled for {$retry->superfrete_id} at {$next_retry}");
191 }
192 }
193
194 } catch (Exception $e) {
195 Logger::log('SuperFrete', "Exception during webhook retry processing: " . $e->getMessage());
196
197 // Update with error
198 $wpdb->update(
199 $this->retry_table,
200 [
201 'retry_count' => $retry->retry_count + 1,
202 'error_message' => $e->getMessage(),
203 'updated_at' => current_time('mysql')
204 ],
205 ['id' => $retry->id],
206 ['%d', '%s', '%s'],
207 ['%d']
208 );
209 }
210 }
211
212 /**
213 * Retry webhook processing
214 *
215 * @param array $payload
216 * @param object $retry_record
217 * @return array
218 */
219 private function retry_webhook_processing($payload, $retry_record)
220 {
221 try {
222 $event_type = $payload['event'];
223 $superfrete_data = $payload['data'];
224 $superfrete_id = $superfrete_data['id'];
225
226 // Find order by SuperFrete ID
227 $order_id = $this->find_order_by_superfrete_id($superfrete_id);
228 if (!$order_id) {
229 return [
230 'success' => false,
231 'error' => "Order not found for SuperFrete ID: $superfrete_id"
232 ];
233 }
234
235 $order = wc_get_order($order_id);
236 if (!$order) {
237 return [
238 'success' => false,
239 'error' => "Invalid order ID: $order_id"
240 ];
241 }
242
243 // Create webhook controller instance to use its methods
244 $webhook_controller = new WebhookController();
245
246 // Use reflection to call private methods
247 $reflection = new ReflectionClass($webhook_controller);
248
249 switch ($event_type) {
250 case 'order.posted':
251 $method = $reflection->getMethod('handle_order_posted');
252 $method->setAccessible(true);
253 $result = $method->invoke($webhook_controller, $order, $superfrete_data, "retry-{$retry_record->id}");
254 break;
255
256 case 'order.delivered':
257 $method = $reflection->getMethod('handle_order_delivered');
258 $method->setAccessible(true);
259 $result = $method->invoke($webhook_controller, $order, $superfrete_data, "retry-{$retry_record->id}");
260 break;
261
262 default:
263 return [
264 'success' => false,
265 'error' => "Unsupported event type: $event_type"
266 ];
267 }
268
269 return $result;
270
271 } catch (Exception $e) {
272 return [
273 'success' => false,
274 'error' => $e->getMessage()
275 ];
276 }
277 }
278
279 /**
280 * Find order by SuperFrete ID
281 *
282 * @param string $superfrete_id
283 * @return int|false
284 */
285 private function find_order_by_superfrete_id($superfrete_id)
286 {
287 global $wpdb;
288
289 $order_id = $wpdb->get_var($wpdb->prepare(
290 "SELECT post_id FROM {$wpdb->postmeta}
291 WHERE meta_key = '_superfrete_id' AND meta_value = %s",
292 $superfrete_id
293 ));
294
295 return $order_id ? (int) $order_id : false;
296 }
297
298 /**
299 * Clean up old retry records
300 */
301 private function cleanup_old_retries()
302 {
303 global $wpdb;
304
305 // Delete completed retries older than 7 days
306 $deleted = $wpdb->query($wpdb->prepare(
307 "DELETE FROM {$this->retry_table}
308 WHERE status IN ('completed', 'failed')
309 AND updated_at < DATE_SUB(NOW(), INTERVAL 7 DAY)"
310 ));
311
312 if ($deleted > 0) {
313 Logger::log('SuperFrete', "Cleaned up {$deleted} old webhook retry records");
314 }
315 }
316
317 /**
318 * Get retry statistics
319 *
320 * @return array
321 */
322 public function get_retry_stats()
323 {
324 global $wpdb;
325
326 $stats = $wpdb->get_row(
327 "SELECT
328 COUNT(*) as total,
329 SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
330 SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
331 SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed
332 FROM {$this->retry_table}
333 WHERE created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)",
334 ARRAY_A
335 );
336
337 return $stats ?: [
338 'total' => 0,
339 'pending' => 0,
340 'completed' => 0,
341 'failed' => 0
342 ];
343 }
344
345 /**
346 * Get recent retry records
347 *
348 * @param int $limit
349 * @return array
350 */
351 public function get_recent_retries($limit = 50)
352 {
353 global $wpdb;
354
355 return $wpdb->get_results($wpdb->prepare(
356 "SELECT * FROM {$this->retry_table}
357 ORDER BY created_at DESC
358 LIMIT %d",
359 $limit
360 ));
361 }
362
363 /**
364 * Manually trigger retry processing
365 */
366 public function manual_process_retries()
367 {
368 Logger::log('SuperFrete', 'Manual webhook retry processing triggered');
369 $this->process_retries();
370 }
371
372 /**
373 * Cancel pending retries for a specific order
374 *
375 * @param int $order_id
376 */
377 public function cancel_retries_for_order($order_id)
378 {
379 global $wpdb;
380
381 $result = $wpdb->update(
382 $this->retry_table,
383 ['status' => 'cancelled'],
384 [
385 'order_id' => $order_id,
386 'status' => 'pending'
387 ],
388 ['%s'],
389 ['%d', '%s']
390 );
391
392 if ($result > 0) {
393 Logger::log('SuperFrete', "Cancelled {$result} pending webhook retries for order {$order_id}");
394 }
395
396 return $result;
397 }
398 }