sequenceDiagram
participant U as Usuário (Desktop)
participant SDK as App React + SDK
participant VS as INJI Verify Service
participant W as Carteira Digital (Celular)
U->>SDK: Clica "Gerar QR Code"
SDK->>VS: POST /v1/verify/vp-request
VS-->>SDK: transactionId + requestId + authorizationDetails
SDK->>U: Renderiza QR Code
U->>W: Escaneia QR Code
W->>VS: POST /v1/verify/vp-submission (credencial)
VS->>VS: Valida credencial
Note over SDK,VS: Long-polling até receber resultado
VS-->>SDK: Resultado
SDK->>U: Callback executado
Integração Web (QR Code)
Objetivo
Implementar verificação de credencial em uma aplicação Web React usando QR Code (fluxo cross-device).
Fluxo Web
Cenário
O usuário está no desktop. O site exibe um QR Code. O usuário abre a Carteira Digital no celular, escaneia o QR Code e autoriza o compartilhamento da credencial.
Passo 1 — Instalar o SDK
npm install @injistack/react-inji-verify-sdk@0.17.0
Importante
É necessário utilizar a versão 0.17.0 do SDK.
Passo 2 — Componente de verificação
// src/components/VerificaIdade.tsx
import { useState } from 'react';
import { OpenID4VPVerification } from '@injistack/react-inji-verify-sdk';
const VERIFY_SERVICE_URL = '/v1/verify';
const CLIENT_ID_DID = 'did:web:localhost:v1:verify';
const presentationDefinition = {
id: 'eca-age-verification',
purpose: 'Verificação de idade conforme o Estatuto da Criança e do Adolescente',
format: { ldp_vc: { proof_type: ['Ed25519Signature2020'] } },
input_descriptors: [
{
id: 'eca credential',
format: { ldp_vc: { proof_type: ['Ed25519Signature2020'] } },
constraints: {
fields: [
{
path: ['$.type'],
filter: { type: 'object', pattern: 'ECACredential' },
},
],
},
},
],
};
export function VerificaIdade() {
const [iniciado, setIniciado] = useState(false);
return (
<div>
<h2>Verificação de Idade</h2>
{!iniciado ? (
<button onClick={() => setIniciado(true)}>Gerar QR Code</button>
) : (
<OpenID4VPVerification
verifyServiceUrl={VERIFY_SERVICE_URL}
clientId={CLIENT_ID_DID}
protocol="openid4vp://"
isSameDeviceFlowEnabled={false}
presentationDefinition={presentationDefinition}
onVPProcessed={(results) => {
const ok = results.some((r: { vcStatus: string }) => r.vcStatus === 'SUCCESS');
console.log('Verificado:', ok);
}}
onError={(error) => console.error('Erro na verificação:', error)}
onQrCodeExpired={() => setIniciado(false)}
/>
)}
</div>
);
}
Passo 3 — Usar o componente
// src/pages/Checkout.tsx
import { VerificaIdade } from '../components/VerificaIdade';
export function Checkout() {
return (
<div>
<h1>Verificação de Credencial</h1>
<VerificaIdade />
</div>
);
}
Versão para produção (com validação no backend)
Para maior segurança, capture o transactionId e valide no backend:
import { OpenID4VPVerification } from '@injistack/react-inji-verify-sdk';
export function VerificaIdadeProd() {
return (
<OpenID4VPVerification
verifyServiceUrl="<VERIFY_BASE_URL>/v1/verify"
clientId="did:web:<SEU_DOMINIO>:v1:verify"
protocol="openid4vp://"
isSameDeviceFlowEnabled={false}
presentationDefinition={presentationDefinition}
onVPProcessed={async (results) => {
const ok = results.some((r: { vcStatus: string }) => r.vcStatus === 'SUCCESS');
if (ok) { /* liberar acesso */ }
}}
onError={(error) => console.error(error)}
onQrCodeExpired={() => console.log('QR expirou')}
/>
);
}
Importante
Para maior segurança, seu backend pode buscar o resultado diretamente em GET <VERIFY_BASE_URL>/v1/verify/vp-result/{transactionId} e decidir se libera o acesso, evitando que o frontend seja adulterado.
Customização do QR Code
<OpenID4VPVerification
// ...
qrCodeStyles={{
size: 300,
bgColor: '#ffffff',
fgColor: '#000000',
}}
triggerElement={<button className="btn-verificar">Verificar Idade</button>}
/>
Props do OpenID4VPVerification
| Prop | Tipo | Obrigatório | Descrição |
|---|---|---|---|
verifyServiceUrl |
string |
Sim | URL do INJI Verify Service |
clientId |
string |
Sim | DID do verificador |
protocol |
string |
Sim | Protocolo ("openid4vp://") |
isSameDeviceFlowEnabled |
boolean |
Não | Habilita fluxo no mesmo dispositivo. Padrão: true — defina false para fluxo cross-device sem web wallet |
presentationDefinition |
object |
Sim* | Presentation Definition inline |
presentationDefinitionId |
string |
Sim* | ID de definição pré-cadastrada |
onVPProcessed |
(results) => void |
Sim | Callback com resultado completo |
onError |
(error) => void |
Sim | Callback de erro |
onQrCodeExpired |
() => void |
Sim | Callback de QR expirado |
qrCodeStyles |
object |
Não | Estilos do QR Code |
* Use presentationDefinition ou presentationDefinitionId, não ambos.
Próximo passo
Para integração mobile, veja Integração Mobile (Deep Link).