Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
91.23% |
52 / 57 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
| NuvemshopAuthService | |
91.23% |
52 / 57 |
|
0.00% |
0 / 4 |
13.11 | |
0.00% |
0 / 1 |
| authorizationUrl | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
| exchangeAuthorizationCode | |
88.24% |
15 / 17 |
|
0.00% |
0 / 1 |
4.03 | |||
| connectStore | |
96.43% |
27 / 28 |
|
0.00% |
0 / 1 |
3 | |||
| parseScopes | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace App\Services\Nuvemshop; |
| 4 | |
| 5 | use App\Enums\IntegrationStatus; |
| 6 | use App\Models\IntegrationProvider; |
| 7 | use App\Models\Store; |
| 8 | use App\Models\StoreIntegration; |
| 9 | use Illuminate\Support\Facades\Http; |
| 10 | use RuntimeException; |
| 11 | |
| 12 | class 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 | } |