Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.23% covered (success)
91.23%
52 / 57
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
NuvemshopAuthService
91.23% covered (success)
91.23%
52 / 57
0.00% covered (danger)
0.00%
0 / 4
13.11
0.00% covered (danger)
0.00%
0 / 1
 authorizationUrl
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 exchangeAuthorizationCode
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
4.03
 connectStore
96.43% covered (success)
96.43%
27 / 28
0.00% covered (danger)
0.00%
0 / 1
3
 parseScopes
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
1<?php
2
3namespace App\Services\Nuvemshop;
4
5use App\Enums\IntegrationStatus;
6use App\Models\IntegrationProvider;
7use App\Models\Store;
8use App\Models\StoreIntegration;
9use Illuminate\Support\Facades\Http;
10use RuntimeException;
11
12class NuvemshopAuthService
13{
14    public function authorizationUrl(?string $state = null): string
15    {
16        $clientId = config('services.nuvemshop.client_id');
17
18        if (blank($clientId)) {
19            throw new RuntimeException('Configure NUVEMSHOP_CLIENT_ID no .env antes de iniciar o OAuth.');
20        }
21
22        $url = 'https://www.tiendanube.com/apps/' . urlencode((string) $clientId) . '/authorize';
23
24        return filled($state)
25            ? $url . '?' . http_build_query(['state' => $state])
26            : $url;
27    }
28
29    public function exchangeAuthorizationCode(string $code): array
30    {
31        $clientId = config('services.nuvemshop.client_id');
32        $clientSecret = config('services.nuvemshop.client_secret');
33
34        if (blank($clientId) || blank($clientSecret)) {
35            throw new RuntimeException('Configure NUVEMSHOP_CLIENT_ID e NUVEMSHOP_CLIENT_SECRET no .env.');
36        }
37
38        $response = Http::acceptJson()
39            ->asJson()
40            ->timeout(30)
41            ->retry(2, 500)
42            ->post('https://www.tiendanube.com/apps/authorize/token', [
43                'client_id' => (string) $clientId,
44                'client_secret' => (string) $clientSecret,
45                'grant_type' => 'authorization_code',
46                'code' => $code,
47            ]);
48
49        if ($response->failed()) {
50            throw new RuntimeException('Falha ao trocar code por access token: ' . $response->body());
51        }
52
53        return $response->json() ?? [];
54    }
55
56    public function connectStore(Store $store, array $payload): StoreIntegration
57    {
58        $provider = IntegrationProvider::query()
59            ->where('slug', 'nuvemshop')
60            ->firstOrFail();
61
62        $externalStoreId = (string) data_get($payload, 'user_id', data_get($payload, 'store_id'));
63        $accessToken = data_get($payload, 'access_token');
64
65        if (blank($externalStoreId) || blank($accessToken)) {
66            throw new RuntimeException('Resposta da Nuvemshop sem user_id/store_id ou access_token.');
67        }
68
69        return StoreIntegration::updateOrCreate(
70            [
71                'store_id' => $store->id,
72                'provider_id' => $provider->id,
73            ],
74            [
75                'tenant_id' => $store->tenant_id,
76                'external_store_id' => $externalStoreId,
77                'access_token' => $accessToken,
78                'refresh_token' => data_get($payload, 'refresh_token'),
79                'scopes' => $this->parseScopes(data_get($payload, 'scope', '')),
80                'status' => IntegrationStatus::Connected->value,
81                'installed_at' => now(),
82                'metadata' => [
83                    'token_type' => data_get($payload, 'token_type'),
84                    'oauth_response' => collect($payload)
85                        ->except(['access_token', 'refresh_token'])
86                        ->toArray(),
87                ],
88            ]
89        );
90    }
91
92    public function parseScopes(mixed $scope): array
93    {
94        if (is_array($scope)) {
95            return array_values(array_filter($scope));
96        }
97
98        return array_values(array_filter(
99            preg_split('/[\s,]+/', (string) $scope) ?: []
100        ));
101    }
102}