Skip to content

Instantly share code, notes, and snippets.

@AdamJLemmon
Last active January 31, 2021 01:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdamJLemmon/1db9316c9b9f04c4b9aa669de96ecaac to your computer and use it in GitHub Desktop.
Save AdamJLemmon/1db9316c9b9f04c4b9aa669de96ecaac to your computer and use it in GitHub Desktop.
// Dependencies
const axios = require('axios')
const jp = require('jsonpath')
// Environment Variables
const API_URL = process.env.API_URL || 'http://localhost:3002'
const VCX_API_KEY = process.env.VCX_API_KEY || '42d34daf-29fc-4703-b52c-82c69f4e1a58'
const ORG_ID = process.env.ORG_ID || '60160d896915b96d1f50c9c2'
// API authZ
const orgId = ORG_ID
const headers = {
'vcx-api-key': VCX_API_KEY
}
// Credential data
// Patient - "required": ["resourceType", "extension", "identifier", "name", "gender", "birthDate"],
const Patient = {
resourceType: "Patient",
extension: [{
url: "http://hl7.org/fhir/StructureDefinition/patient-nationality",
code: { "text": "SG" }
}],
identifier: [{
type: { "text": "NRIC" },
value: "S9098989Z"
},{
type: "PPN",
value: "HN1234"
}],
name: [{
text: "Tan Chen Chen"
}],
gender: "female",
birthDate: "1990-01-15"
}
// Specimen - "required": ["resourceType", "type", "collection"],
const Specimen = {
resourceType: "Specimen",
type: {
coding: [{
system: "http://snomed.info/sct",
code: "258500001",
display: "Nasopharyngeal swab"
}]
},
collection: {
collectedDateTime: "2020-09-27T06:15:00Z"
}
}
// Observation - "required": [ "resourceType", "identifier", "code", "valueCodeableConcept", "effectiveDateTime", "qualification", "status" ],
const Observation = {
"resourceType": "Observation",
"identifier": [{
"value": "123456789",
"type": "ACSN"
}],
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "94531-1",
"display": "Reverse transcription polymerase chain reaction (rRT-PCR) test"
}]
},
"valueCodeableConcept": {
"coding": [{
"system": "http://snomed.info/sct",
"code": "260385009",
"display": "Negative"
}]
},
"effectiveDateTime": "2020-09-28T06:15:00Z",
"status": "final",
"performer": {
"name": [{
"text": "Dr Michael Lim"
}]
},
"qualification": [{
"identifier": "MCR 123214",
"issuer": "MOH"
}]
}
// Organization - "required": ["resourceType", "name", "type", "contact"],
// Organization: Healtcare Provider
const Organization1 = {
"resourceType": "Organization",
"name": "MacRitchie Medical Clinic",
"type": "Licensed Healthcare Provider",
"contact": {
"telecom": [{
"system": "phone",
"value": "+6563113111"
}],
"address": {
"type": "physical",
"use": "work",
"text": "MacRitchie Hospital Thomson Road Singapore 123000"
}
}
}
// Organization: Quest
const Organization2 = {
"resourceType": "Organization",
"name": "Quest Laboratories",
"type": "Accredited Laboratory",
"id": "quest-labs-0163",
"contact": {
"telecom": [{
"system": "phone",
"value": "+6563113111"
}],
"address": {
"type": "physical",
"use": "work",
"text": "Quest Laboratories Address"
},
}
}
// Package it all up into the credential
const credential = {
"type": [ "VerifiableCredential", "HealthPassportBundleCredentialV1" ],
credentialSubject: {
data: {
// "required": ["id", "name", "validFrom", "fhirVersion", "fhirBundle"]
'@type': 'BundleContainer',
id: 'Unique test ID',
name: 'HealthCert',
validFrom: new Date().toISOString(),
fhirVersion: '4.0.1',
fhirBundle: {
'@type': 'Bundle',
resourceType: 'Bundle',
type: 'collection',
entry: [ Patient, Specimen, Observation, Organization1, Organization2 ],
},
},
},
}
/**
* Complete flow to issue a HealthCerts Verifiable Credential
*/
const issueCredential = async ({ orgId, credential, options }) => {
const body = { credential, options, orgId }
const url = `${API_URL}/provider/issueCustomCredential`
const response = await axios.post(url, body, { headers })
return response.data;
}
const getIssuedCredentials = async ({ orgId, query }) => {
const searchParams = new URLSearchParams({ orgId, ...query })
const url = `${API_URL}/provider/getIssuedCredentials?${searchParams.toString()}`
const response = await axios.get(url, { headers })
return response.data;
}
const run = async () => {
/**
* Will return:
* 1. Credential download URL, also sent in SMS (protected by pasport number)
* 2. QR Code access URL, also sent in SMS (protected by pasport number)
* 3. QR Code base64 raw data (to be included on PDF)
*/
const options = {
responseType: [
'walletInstallAndCredentialDownloadUrl',
'encryptedCredentialPresentationProtectedQrCodeUrl',
'encryptedCredentialPresentationQrCode'
],
indexes: [{
property: 'labNumber',
path: '$.credentialSubject.data.fhirBundle.entry[4].id'
}, {
property: 'ppn',
path: '$.credentialSubject.data.fhirBundle.entry[0].identifier[1].value'
}],
requiredUserAuthentication: [{
property: 'ppn',
name: 'Passport Number',
description: 'Passport Number.',
acceptedValues: ['1234'],
reason: 'authentication'
}],
holderContactDetails: {
firstName: 'Adam',
type: 'sms',
to: '+16472399319'
},
}
const response = await issueCredential({ orgId, credential, options })
console.log({ response })
// Now confirm you can get the response later with the indexes
const labNumber = jp.query(credential, options.indexes[0].path, 1)[0]
const ppn = jp.query(credential, options.indexes[1].path, 1)[0]
const query = { labNumber, ppn }
const { savedCredentials } = await getIssuedCredentials({ orgId, query })
console.log({ response: savedCredentials[0].response })
}
run()
// VCX_API_KEY=663b4151-2b80-4d46-80c2-c9ae3728a134 ORG_ID=5fc7d717da620b611af5de4f node apiProviderHealthCert.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment