Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.67% covered (warning)
66.67%
24 / 36
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
NuvemshopWebhookRegistrationService
66.67% covered (warning)
66.67%
24 / 36
42.86% covered (danger)
42.86%
3 / 7
23.33
0.00% covered (danger)
0.00%
0 / 1
 register
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
3.05
 registerDefaultOrderWebhooks
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 list
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 delete
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 assertIntegration
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
4.12
 http
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 url
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Services\Nuvemshop;
4
5use App\Models\StoreIntegration;
6use Illuminate\Support\Facades\Http;
7use RuntimeException;
8
9class NuvemshopWebhookRegistrationService
10{
11    public const DEFAULT_ORDER_EVENTS = [
12        'order/created',
13        'order/paid',
14        'order/updated',
15        'order/cancelled',
16        'order/voided',
17    ];
18
19    public function register(StoreIntegration $integration, string $event, string $url): array
20    {
21        $this->assertIntegration($integration);
22
23        if (! str_starts_with($url, 'https://')) {
24            throw new RuntimeException('A URL do webhook precisa ser HTTPS pública. Use ngrok/Cloudflare Tunnel para ambiente local.');
25        }
26
27        $response = $this->http($integration)
28            ->post($this->url($integration, '/webhooks'), [
29                'event' => $event,
30                'url' => $url,
31            ]);
32
33        if ($response->failed()) {
34            throw new RuntimeException('Falha ao cadastrar webhook '.$event.': '.$response->body());
35        }
36
37        return $response->json() ?? [];
38    }
39
40    public function registerDefaultOrderWebhooks(StoreIntegration $integration, string $url): array
41    {
42        $created = [];
43
44        foreach (self::DEFAULT_ORDER_EVENTS as $event) {
45            $created[$event] = $this->register($integration, $event, $url);
46        }
47
48        return $created;
49    }
50
51    public function list(StoreIntegration $integration): array
52    {
53        $this->assertIntegration($integration);
54
55        $response = $this->http($integration)->get($this->url($integration, '/webhooks'));
56
57        if ($response->failed()) {
58            throw new RuntimeException('Falha ao listar webhooks: '.$response->body());
59        }
60
61        return $response->json() ?? [];
62    }
63
64    public function delete(StoreIntegration $integration, string|int $webhookId): void
65    {
66        $this->assertIntegration($integration);
67
68        $response = $this->http($integration)->delete($this->url($integration, '/webhooks/'.$webhookId));
69
70        if ($response->failed() && $response->status() !== 404) {
71            throw new RuntimeException('Falha ao excluir webhook '.$webhookId.': '.$response->body());
72        }
73    }
74
75    private function assertIntegration(StoreIntegration $integration): void
76    {
77        if (blank($integration->external_store_id) || blank($integration->access_token)) {
78            throw new RuntimeException('Integração Nuvemshop sem external_store_id ou access_token.');
79        }
80    }
81
82    private function http(StoreIntegration $integration): \Illuminate\Http\Client\PendingRequest
83    {
84        return Http::acceptJson()
85            ->asJson()
86            ->withToken($integration->access_token)
87            ->withHeaders([
88                'User-Agent' => config('services.nuvemshop.user_agent', 'Orvox Influencers Test Suite'),
89            ])
90            ->timeout(30)
91            ->retry(2, 500);
92    }
93
94    private function url(StoreIntegration $integration, string $path): string
95    {
96        $base = rtrim((string) config('services.nuvemshop.api_base_url', 'https://api.tiendanube.com/v1'), '/');
97
98        return $base.'/'.$integration->external_store_id.'/'.ltrim($path, '/');
99    }
100}