Pular para o conteúdo

SDK PHP

O pliic/pliic-php é o SDK oficial em PHP. Ele é para quem quer integrar o Pliic nativamente no backend: em vez de embutir o widget, o seu sistema cria sugestões e tickets em nome dos seus usuários, exibe o mural com estado de voto, responde tickets e consome webhooks, tudo pela API REST.

Se o widget embutido já atende, você não precisa do SDK. Use quando quiser controlar 100% da experiência dentro do seu produto.

Esta página é a referência rápida de cada chamada. Para a receita completa de como montar um painel de ajuda nativo com o SDK, incluindo proxy de chave, identidade do usuário e posse de ticket, veja Construa seu helper nativo com o SDK PHP.

Terminal window
composer require pliic/pliic-php

Requer PHP 8.2+ com ext-curl e ext-json. Nenhuma outra dependência.

Crie o cliente com a chave secreta do app (sk_live_..., na aba Instalação do app, onde também fica o início rápido em PHP):

use Pliic\PliicClient;
$pliic = new PliicClient('sk_live_...');

Os endpoints exigem escopos na chave (suggestions:read, suggestions:write, tickets:read, tickets:write, etc.) e o recurso de API disponível no seu plano.

Toda escrita aceita um objeto user com a identidade do usuário no seu sistema. O Pliic cria ou reutiliza o usuário correspondente automaticamente (se o e-mail já existir, é a mesma pessoa):

$user = ['id' => 'u_123', 'name' => 'Ana', 'email' => '[email protected]'];
$pliic->suggestions->create([
'user' => $user,
'title' => 'Modo escuro',
'description' => 'Seria mais confortável à noite.',
]);

Você não precisa saber o id interno do Pliic, o id é o do seu próprio banco.

// Mural com estado de voto do usuário atual
$pliic->suggestions->list(['status' => 'planned', 'search' => 'escuro', 'user_id' => 'u_123']);
$pliic->suggestions->get(42, ['user_id' => 'u_123']); // inclui user_has_voted
$pliic->suggestions->vote(42, ['user' => $user]); // vota; chamar de novo desfaz
$pliic->suggestions->comments(42);
$pliic->suggestions->addComment(42, ['user' => $user, 'body' => 'Ótima ideia!']);
$pliic->tickets->list(['user_id' => 'u_123']); // tickets daquele usuário
$pliic->tickets->create(['user' => $user, 'subject' => 'Erro no checkout', 'body' => '...', 'type' => 'bug']);
$pliic->tickets->get(7, ['user_id' => 'u_123']); // 404 se o ticket não for de u_123
$pliic->tickets->reply(7, ['user' => $user, 'body' => 'Mais detalhes aqui...']);

Passar user_id/user_email em tickets->get() restringe a busca àquele autor: se o ticket existir mas for de outra pessoa, a resposta é 404 (desde a versão 1.0.1 do SDK), o mesmo status de “não existe”. Sem esse parâmetro, a chamada devolve qualquer ticket do app pelo id, então use-o sempre que for o próprio usuário final consultando o ticket.

Só o autor do ticket pode responder por ele, e notas internas da sua equipe nunca aparecem na conversa retornada.

Se você também usa o widget embutido, o SDK minta o userToken no servidor:

use Pliic\UserToken;
$token = UserToken::mint($chaveSecreta, [
'id' => 'u_123',
'name' => 'Ana',
'email' => '[email protected]',
]);

Entregue $token ao frontend como userToken do widget.

Verifique a assinatura antes de confiar em qualquer payload:

use Pliic\Webhook;
use Pliic\Exceptions\SignatureVerificationException;
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->header('X-Pliic-Signature'),
$segredoDoEndpoint, // whsec_...
);
} catch (SignatureVerificationException $e) {
abort(400);
}
match ($event->type) {
'suggestion.created' => processarSugestao($event->data),
'ticket.created' => processarTicket($event->data),
default => null,
};

Detalhes do formato da assinatura e dos eventos disponíveis: Webhooks.

Falhas da API viram exceções tipadas, todas filhas de Pliic\Exceptions\ApiErrorException:

StatusExceção
401AuthenticationException
403 + error: insufficient_scopeInsufficientScopeException (filha de PermissionException)
403PermissionException (recurso não incluído no plano e demais recusas)
404NotFoundException
422ValidationException ($e->errors() traz os erros por campo)
429RateLimitException

O mapeamento usa o código estável error da resposta, nunca o texto da mensagem. Mudar a redação de um erro não quebra os seus catch. Falhas de rede viram Pliic\Exceptions\TransportException.

Toda chave nova nasce somente leitura: ela tem suggestions:read e tickets:read, mais nada. Por isso o seu primeiro create() falha com 403 até alguém habilitar o escopo de escrita. Não é erro no payload.

use Pliic\Exceptions\InsufficientScopeException;
try {
$pliic->tickets->create(['user' => $user, 'subject' => 'Não consigo entrar']);
} catch (InsufficientScopeException $e) {
$e->requiredScope(); // 'tickets:write'
$e->grantedScopes(); // ['suggestions:read', 'tickets:read']
$e->manageScopesUrl(); // link direto para Configurações → Chaves de API → Escopos
$e->docsUrl();
}

Como InsufficientScopeException estende PermissionException, quem já captura PermissionException continua capturando. Use a exceção específica só onde você quiser separar “a chave não tem permissão” de “o plano não inclui esse recurso”.

Para liberar: Configurações → Chaves de API → Escopos, no app a que a chave pertence. Detalhes em Chaves de API.

Desde a versão 1.1.0, o SDK vem com um cliente HTTP falso pronto para os seus testes: Pliic\Testing\FakeHttpClient. Não escreva o seu próprio fake para simular a API — os formatos de resposta divergem sutilmente do que a API real devolve (inclusive campos mais novos, como author), e é exatamente esse tipo de bug de teste-passa-mas-produção-quebra que o FakeHttpClient existe para eliminar: os payloads que ele usa são conferidos contra o OpenAPI real em CI, então não ficam defasados.

Injete o fake no lugar do transporte HTTP de verdade:

use Pliic\PliicClient;
use Pliic\Testing\FakeHttpClient;
$fake = new FakeHttpClient();
$pliic = new PliicClient('sk_test_fake', 'https://pliic.com', $fake);
$pliic->suggestions->list(); // lista realista, sem nenhuma configuração

Quando um teste precisa de um dado específico, semeie o payload:

$fake->seedSuggestion(['id' => 42, 'title' => 'Modo escuro', 'vote_count' => 12]);
$pliic->suggestions->get(42); // devolve a sugestão semeada
$fake->seedError(422, 'Invalid', ['title' => ['Título já existe.']]);
$pliic->suggestions->create(['user' => $user, 'title' => 'Duplicada']); // lança ValidationException
$fake->seedInsufficientScope('tickets:write');
$pliic->tickets->create(['user' => $user, 'subject' => 'Oi']); // lança InsufficientScopeException

ownedByEmail()/ownedByUserId() simulam a mesma regra de posse da API real (buscar o ticket de outro user_email/user_id dá 404). Depois de configurar um dos dois, o fake passa a negar qualquer requisição que não carregue esse parâmetro exato — inclusive uma chamada que, contra a API de verdade, não seria restrita (a API só valida posse quando user_id/user_email é enviado). Use um new FakeHttpClient() novo entre cenários que precisem dos dois comportamentos no mesmo teste.

failNextWithTransportError() simula uma falha de rede só na próxima chamada. E dá para checar o que foi enviado:

$fake->assertRequested('POST', '/suggestions/42/vote');
$fake->assertRequestCount(2);
expect($fake->lastRequestBody())->toBe(['user' => $user, 'title' => 'Modo escuro']);

$fake->requests guarda toda chamada feita (method, url, headers, body). Se precisar de um payload fora do fake — por exemplo, para comparar num teste de controller — Pliic\Testing\Fixtures expõe cada um diretamente (Fixtures::suggestion(), Fixtures::ticket(), …).

Desde a versão 1.2.0, o SDK traz uma ponte opcional para Laravel, descoberta automaticamente pelo composer: ela só carrega quando a sua aplicação hospedeira é um projeto Laravel, e nunca adiciona illuminate/support como dependência obrigatória do SDK, então quem usa PHP puro não é afetado. Requer Illuminate ^11 ou superior.

1. Instale (mesmo comando de sempre):

Terminal window
composer require pliic/pliic-php

2. Publique a config e defina as variáveis de ambiente:

Terminal window
php artisan vendor:publish --tag=pliic-config
PLIIC_API_KEY=sk_live_...
PLIIC_BASE_URL=https://pliic.com
PLIIC_WEBHOOK_SECRET=whsec_...

Pliic\PliicClient já sai registrado como singleton no container, resolvível em qualquer lugar da aplicação:

use Pliic\PliicClient;
$pliic = app(PliicClient::class);

3. Registre a rota de webhook e escute o evento:

use Pliic\Laravel\Pliic;
Pliic::webhooks('/webhooks/pliic'); // POST /webhooks/pliic

A rota já verifica o X-Pliic-Signature por você e dispara Pliic\Laravel\Events\WebhookReceived — sua aplicação nunca lida com o payload bruto nem com a checagem de assinatura. Ela também já sai isenta de CSRF, então pode ficar tranquilamente em routes/web.php sem configuração extra.

use Illuminate\Support\Facades\Event;
use Pliic\Laravel\Events\WebhookReceived;
Event::listen(WebhookReceived::class, function (WebhookReceived $received): void {
match ($received->event->type) {
'suggestion.created' => notificarEquipeDeNovaSugestao($received->event->data),
default => null,
};
});

author vs sender: evitando notificar a própria pessoa

Seção intitulada “author vs sender: evitando notificar a própria pessoa”

Todo payload de webhook traz um author (dono do registro) e um sender (quem disparou este evento específico) — podem ser a mesma pessoa ou não. Compare os dois antes de notificar, para não avisar alguém sobre a própria ação:

use Illuminate\Support\Facades\Event;
use Pliic\Laravel\Events\WebhookReceived;
Event::listen(WebhookReceived::class, function (WebhookReceived $received): void {
if ($received->event->type !== 'suggestion.commented') {
return;
}
$author = $received->event->data['author']; // ['external_id' => ..., 'name' => ...]
$sender = $received->event->data['sender']; // ['type' => 'app_user'|'member', 'external_id' => ..., 'name' => ...]
if ($sender['external_id'] === $author['external_id']) {
return; // o autor comentou na própria sugestão — nada a notificar
}
notificar($author, "{$sender['name']} comentou na sua sugestão.");
});

sender['type'] vem 'member' quando alguém da sua equipe agiu pelo painel do Pliic (external_id sempre null, membros não são usuários do app) e 'app_user' quando foi o próprio usuário final. Mais sobre os dois campos: Identificando quem fez o quê.

Para construir a interface no navegador (em vez do backend), use o @pliic/sdk, que fala com a API do widget usando a chave pública.