Skip to main content

QR Code Component Integration Guide

Introduction

This guide provides instructions for integrating a QR code component into your web page. The component will display a QR code, poll a specified API, and update its status based on the API response. It will also provide options to customize the polling behavior and display a restart button when polling times out.

Screenshot 2024-07-09 at 15.49.24.png

Integration Steps

1. Include Required Files

Add the following HTML to include the necessary files in your web page:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>QR Code Component</title>
    <style>
        .qr-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin: 0 auto;
            max-width: 100%;
            padding: 20px;
        }
        .status-message {
            margin-top: 20px;
            text-align: center;
        }
        .status-image, #qr-code {
            margin-top: 10px;
            max-width: 100%;
        }
    </style>
</head>
<body>
    <div id="qr-component" class="qr-container"></div>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/qrcode/build/qrcode.min.js"></script>
    <script src="qrComponent.js"></script>
    <script src="options.js"></script>
    <script>
        // Esempio di configurazione della componente
        const options = options
        // Inizializzazione della componente
        loadQRComponent(options);
    </script>
</body>
</html>

2. Configure the Component

Create an options.js file with the following content to configure the component:

const options = {
    containerId: 'qr-component',
    lang: 'it',
    type: 'verify', // Cambia a 'issue' se necessario
    qrText: 'https://example.com',
    qrSize: 150,
    apiConfig: {
        method: 'POST',
        url: 'https://api.example.com/status',
        headers: {
            'Authorization': 'Bearer token',
            'Content-Type': 'application/json'
        },
        body: {
            key: 'value'
        }
    },
    statuses: {
        verify: {
            CREATED: {
                isFinal: false,
                title: { en: 'QR Code Created', it: 'QR Code Creato' },
                message: { en: 'Please scan the QR code.', it: 'Scansiona il QR code.' }
            },
            READ: {
                isFinal: false,
                title: { en: 'QR Code Read', it: 'QR Code Letto' },
                message: { en: 'QR code has been read.', it: 'Il QR code è stato letto.' }
            },
            SUCCESS: {
                isFinal: false,
                title: { en: 'Verification Successful', it: 'Verifica riuscita' },
                message: { en: 'The credential has been verified successfully.', it: 'La credenziale è stata verificata con successo.' }
            },
            FAILED: {
                isFinal: true,
                title: { en: 'Verification Failed', it: 'Verifica fallita' },
                message: { en: 'The verification has failed.', it: 'La verifica è fallita.' },
                imageUrl: 'https://example.com/error.png'
            },
            BL_SUCCESS: {
                isFinal: true,
                title: { en: 'BL Check Successful', it: 'Controllo BL riuscito' },
                message: { en: 'The BL check was successful.', it: 'Il controllo BL è riuscito.' },
                imageUrl: 'https://example.com/success.png'
            },
            BL_FAILED: {
                isFinal: true,
                title: { en: 'BL Check Failed', it: 'Controllo BL fallito' },
                message: { en: 'The BL check has failed.', it: 'Il controllo BL è fallito.' },
                imageUrl: 'https://example.com/error.png'
            },
            COMPLETED: {
                isFinal: true,
                title: { en: 'Verification Completed', it: 'Verifica completata' },
                message: { en: 'The transaction is completed.', it: 'La transazione è completata.' },
                imageUrl: 'https://example.com/success.png'
            },
            EXPIRED: {
                isFinal: true,
                title: { en: 'QR Code Expired', it: 'QR Code Scaduto' },
                message: { en: 'The QR code has expired.', it: 'Il QR code è scaduto.' },
                imageUrl: 'https://example.com/expired.png'
            }
        },
        issue: {
            CREATED: {
                isFinal: false,
                title: { en: 'QR Code Created', it: 'QR Code Creato' },
                message: { en: 'Please scan the QR code.', it: 'Scansiona il QR code.' }
            },
            READ: {
                isFinal: false,
                title: { en: 'QR Code Read', it: 'QR Code Letto' },
                message: { en: 'QR code has been read.', it: 'Il QR code è stato letto.' }
            },
            DELIVERED: {
                isFinal: false,
                title: { en: 'Credential Delivered', it: 'Credenziale Consegnata' },
                message: { en: 'The credential has been delivered to the wallet.', it: 'La credenziale è stata consegnata al wallet.' }
            },
            ACCEPTED: {
                isFinal: true,
                title: { en: 'Credential Accepted', it: 'Credenziale Accettata' },
                message: { en: 'The wallet has accepted the credential.', it: 'Il wallet ha accettato la credenziale.' },
                imageUrl: 'https://example.com/success.png'
            },
            REJECTED: {
                isFinal: true,
                title: { en: 'Credential Rejected', it: 'Credenziale Rifiutata' },
                message: { en: 'The wallet has rejected the credential.', it: 'Il wallet ha rifiutato la credenziale.' },
                imageUrl: 'https://example.com/error.png'
            },
            FAILURE: {
                isFinal: true,
                title: { en: 'Issue Failed', it: 'Rilascio Fallito' },
                message: { en: 'The issue has failed.', it: 'Il rilascio è fallito.' },
                imageUrl: 'https://example.com/error.png'
            },
            ERROR: {
                isFinal: true,
                title: { en: 'Transaction Error', it: 'Errore di Transazione' },
                message: { en: 'There was an error in the transaction.', it: 'Si è verificato un errore nella transazione.' },
                imageUrl: 'https://example.com/error.png'
            },
            EXPIRED: {
                isFinal: true,
                title: { en: 'QR Code Expired', it: 'QR Code Scaduto' },
                message: { en: 'The QR code has expired.', it: 'Il QR code è scaduto.' },
                imageUrl: 'https://example.com/expired.png'
            }
        }
    },
    pollingInterval: 5000,
    pollingTimeout: 30000, // Tempo massimo di polling in millisecondi
    restartButton: {
        text: 'Restart Polling',
        style: {
            backgroundColor: '#ff6600',
            color: '#fff',
            padding: '10px 20px',
            textDecoration: 'none',
            borderRadius: '5px',
            display: 'inline-block',
            marginTop: '20px',
            cursor: 'pointer'
        }
    }
};

3. QR Component Logic

Create a qrComponent.js file with the following content:

class QRComponent {
    constructor(options) {
        this.container = document.getElementById(options.containerId);
        this.lang = options.lang || 'en';
        this.type = options.type || 'verify';
        this.qrText = options.qrText || '';
        this.qrSize = options.qrSize || 128;
        this.apiConfig = options.apiConfig || {};
        this.statuses = options.statuses || {};
        this.pollingInterval = options.pollingInterval || 5000;
        this.pollingTimeout = options.pollingTimeout || 30000;
        this.restartButton = options.restartButton || { text: 'Restart Polling', style: {} };
        this.pollingTimeoutId = null;
        this.pollingStartTime = null;

        this.init();
    }

    init() {
        this.createQRCode();
        this.startPolling();
    }

    createQRCode() {
        const qrContainer = document.createElement('canvas');
        qrContainer.id = 'qr-code';
        this.container.appendChild(qrContainer);
        QRCode.toCanvas(qrContainer, this.qrText, { width: this.qrSize }, (error) => {
            if (error) console.error(error);
        });
    }

    startPolling() {
        this.pollingStartTime = Date.now();
        this.pollAPI();
    }

    stopPolling() {
        clearTimeout(this.pollingTimeoutId);
        this.showRestartButton();
    }

    showRestartButton() {
        const existingButton = this.container.querySelector('.restart-button');
        if (existingButton) {
            existingButton.remove();
        }

        const button = document.createElement('button');
        button.className = 'restart-button';
        button.innerText = this.restartButton.text;
        Object.assign(button.style, this.restartButton.style);
        button.addEventListener('click', () => {
            button.remove();
            this.container.querySelector('.status-message')?.remove();
            this.container.querySelector('.status-image')?.remove();
            this.createQRCode();
            this.startPolling();
        });

        this.container.appendChild(button);
    }

    async pollAPI() {
        if (Date.now() - this.pollingStartTime >= this.pollingTimeout) {
            this.stopPolling();
            return;
        }
        
        try {
            const response = await this.makeAPICall();
            let status = response.data.status.toUpperCase();
            this.updateStatus(status);
            if (!this.statuses[this.type][status].isFinal) {
                this.pollingTimeoutId = setTimeout(() => this.pollAPI(), this.pollingInterval);
            } else {
                this.stopPolling();
            }
        } catch (error) {
            console.error('API call failed:', error);
            this.pollingTimeoutId = setTimeout(() => this.pollAPI(), this.pollingInterval);
        }
    }

    async makeAPICall() {
        const { method, url, headers, body } = this.apiConfig;
        return axios({
            method: method,
            url: url,
            headers: headers,
            data: method === 'POST' ? body : null
        });
    }

    updateStatus(status) {
        const statusInfo = this.statuses[this.type][status];
        const title = statusInfo.title[this.lang] || statusInfo.title['en'];
        const message = statusInfo.message[this.lang] || statusInfo.message['en'];
        const imageUrl = statusInfo.imageUrl;

        this.container.querySelector('.status-message')?.remove();

        const messageContainer = document.createElement('div');
        messageContainer.className = 'status-message';
        messageContainer.innerHTML = `<h3>${title}</h3><p>${message}</p>`;
        this.container.appendChild(messageContainer);

        const qrCodeElement = document.getElementById('qr-code');

        // Aggiungi l'immagine solo se è presente e lo status non è CREATED
        if (imageUrl && status !== 'CREATED') {
            const image = document.createElement('img');
            image.className = 'status-image';
            image.src = imageUrl;
            image.id = 'qr-code';
            qrCodeElement.replaceWith(image);
        } else {
            qrCodeElement.style.display = 'block';
        }
    }
}

function loadQRComponent(params) {
    new QRComponent(params);
}

Configuration Fields

containerId (String) The ID of the container element where the QR code component will be rendered.

lang (String) The language for the status messages. Default is 'en'.

type (String) The type of operation, either 'verify' or 'issue'.

qrText (String) The text to encode in the QR code. Should be pd_uri, multipleuse or offer_uri

qrSize (Number) The size of the QR code in pixels.

apiConfig (Object) Configuration for the API polling.

  • method: HTTP method (e.g., 'GET', 'POST').
  • url: The URL of the API endpoint.
  • headers: An object containing request headers.
  • body: An object containing the request body (used for POST requests).

statuses (Object) Defines the statuses for 'verify' and 'issue' operations. Each status object should have the following fields:

  • isFinal: Boolean indicating if the status is final.
  • title: An object with localized titles.
  • message: An object with localized messages.
  • imageUrl: (Optional) The URL of an image to display for the status.

pollingInterval (Number) The interval between polling requests in milliseconds.

pollingTimeout (Number) The maximum time to poll the API in milliseconds.

restartButton (Object) Configuration for the restart button displayed when polling times out.

  • text: The text to display on the button.
  • style: An object containing CSS styles for the button.