bam-ticketing-sdk
Version:
SDK for B.A.M Ticketing API
128 lines (118 loc) • 3.91 kB
text/typescript
import base45 from 'base45'
import { KJUR } from 'jsrsasign'
import { parseTicketId } from '../blockchain/utils'
import { sign, verify } from '../utils/certificate'
import { createInvalidateCall, createInvalidatePayload, TickedIds } from '..'
/**
* Encodes the payload and the signature into the expected QR code payload
*
* @param payload JSON stringified payload
* @param signatureHex Hex encoded signature of the payload
* @returns base 45 encoded string
*/
export function encodeQrPayload(payload: string, signatureHex: string) {
const payloadBuffer = Buffer.from(payload)
const signatureBuffer = Buffer.from(signatureHex, 'hex')
const payloadEncoded = base45.encode(payloadBuffer)
const signatureEncoded = base45.encode(signatureBuffer)
return `${payloadEncoded}:::${signatureEncoded}`
}
/**
* Decode a base 45 encoded QR code payload
*
* @param encodedPayload what you scanned from the QR code
* @returns payload and its signature
*/
export function decodeQrPayload(encodedPayload: string) {
const [payloadEncoded, signatureEncoded] = encodedPayload.split(':::')
const payloadBuffer = base45.decode(payloadEncoded)
const signatureBuffer = base45.decode(signatureEncoded)
const payload = payloadBuffer.toString()
const signature = signatureBuffer.toString('hex')
return {
payload,
signature,
}
}
type TicketQrContent = {
id: string
}
/**
* Signs and encodes ticket data into the string which will be put in the QR code
*
* @param eventId event to which the ticket belongs
* @param ticketConfigId ticket config of the ticket
* @param sequenceNumber the serial number of the ticket
* @param privateKey the private key which is used to sign the ticket
* @returns string to display as a tickets QR code
*/
export function createQrPayloadForTicket(
eventId: number,
ticketConfigId: number,
sequenceNumber: number,
privateKey: string | KJUR.crypto.ECDSA
): string {
// Encode the ticket ID
const ticketId = `E${eventId}TC${ticketConfigId}T${sequenceNumber}`
const payload = JSON.stringify({
id: ticketId,
})
const signature = sign(payload, privateKey)
return encodeQrPayload(payload, signature)
}
/**
* Decodes, verifies the signature of the QR code payload and constructs the blockchain
* function call to invalidate the ticket
*
* @param qrCodePayload content of the QR code
* @param invalidationDate the time at which the ticket was scanned
* @param publicKey key used to verify the ticket signature, if specified
* @returns blockchain function call for ticket invalidation
* @throws Error if the signature does not match the payload
*/
export function qrToTicketInvalidateCall(
qrCodePayload: string,
invalidationDate: Date,
publicKey?: string | KJUR.crypto.ECDSA
) {
const qrContent = decodeQrPayload(qrCodePayload)
if (publicKey) {
const signatureValid = verify(
qrContent.payload,
qrContent.signature,
publicKey
)
if (!signatureValid) {
throw new Error('Invalid ticket signature')
}
}
const payload = JSON.parse(qrContent.payload) as TicketQrContent
const args = createInvalidatePayload(payload.id, invalidationDate)
return createInvalidateCall(args)
}
/**
* Decodes and verifies the signature of the QR code payload and returns the IDs
*
* @param qrCodePayload content of the QR code
* @param publicKey key used to verify the ticket signature, if specified
* @returns ticket IDs
* @throws Error if the signature does not match the payload
*/
export function qrToTicketData(
qrCodePayload: string,
publicKey?: string | KJUR.crypto.ECDSA
): TickedIds {
const qrContent = decodeQrPayload(qrCodePayload)
if (publicKey) {
const signatureValid = verify(
qrContent.payload,
qrContent.signature,
publicKey
)
if (!signatureValid) {
throw new Error('Invalid ticket signature')
}
}
const payload = JSON.parse(qrContent.payload) as TicketQrContent
return parseTicketId(payload.id)
}