diff --git a/client/src/App.tsx b/client/src/App.tsx index a0640fd..fab8a63 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,6 +3,7 @@ import Header from '../src/components/header'; import Patient from '../src/components/patient'; import PatientData from './components/patientData'; import Records from './components/records'; +import InsuranceCard from './components/insuranceCard'; import { BrowserRouter as Router} from "react-router-dom"; import { TabPanel, Tabs } from '@cmsgov/design-system'; @@ -24,6 +25,7 @@ function App() { {} + {}
diff --git a/client/src/components/insuranceCard.tsx b/client/src/components/insuranceCard.tsx new file mode 100644 index 0000000..d7002e5 --- /dev/null +++ b/client/src/components/insuranceCard.tsx @@ -0,0 +1,111 @@ +import { Table, TableCaption, TableRow, TableCell, TableHead, TableBody } from '@cmsgov/design-system'; +import React, { useEffect, useState } from 'react'; +import * as process from 'process'; + +export type InsuranceCardField = { + label: string, + value: string +} + +export type ErrorResponse = { + type: string, + content: string, +} + +export default function InsuranceCard() { + const [fields, setFields] = useState([]); + const [message, setMessage] = useState(); + + /* + * DEVELOPER NOTES: + * The $generate-digital-insurance-card operation returns a FHIR Bundle containing + * CARIN Digital Insurance Card (C4DIC) resources (Patient, Coverage, Organization). + * Here we pull out a few common fields for display purposes. You will want to + * inspect the actual Bundle returned by your environment and adjust the parsing + * to fit the fields your application needs. + */ + useEffect(() => { + const test_url = process.env.TEST_APP_API_URL ? process.env.TEST_APP_API_URL : ''; + fetch(`${test_url}/api/data/insurancecard`) + .then(res => { + return res.json(); + }).then(insuranceCardData => { + if (insuranceCardData.entry) { + const coverageEntry = insuranceCardData.entry.find( + (e: any) => e.resource?.resourceType === 'Coverage' + ); + const organizationEntry = insuranceCardData.entry.find( + (e: any) => e.resource?.resourceType === 'Organization' + ); + const coverage = coverageEntry?.resource; + const organization = organizationEntry?.resource; + + const cardFields: InsuranceCardField[] = [ + { label: 'Payer', value: organization?.name || 'Unknown' }, + { label: 'Member ID', value: coverage?.subscriberId || 'Unknown' }, + { label: 'Plan', value: coverage?.class?.[0]?.name || 'Unknown' }, + { label: 'Group Number', value: coverage?.class?.[0]?.value || 'Unknown' }, + ]; + setFields(cardFields); + } else { + if (insuranceCardData.message) { + setMessage({ "type": "error", "content": insuranceCardData.message || "Unknown" }) + } + } + }); + }, []) + + if (message) { + return ( +
+ + Error Response + + + Type + Content + + + + + + {message.type} + + + {message.content} + + + +
+
+ ); + } else { + return ( +
+ + Digital Insurance Card + + + Field + Value + + + + {fields.map(field => { + return ( + + + {field.label} + + + {field.value} + + + ) + })} + +
+
+ ); + } +} diff --git a/server/index.ts b/server/index.ts index 783fbd7..554ce53 100644 --- a/server/index.ts +++ b/server/index.ts @@ -5,12 +5,14 @@ import * as fs from "fs"; interface User { authToken?: AuthorizationToken, eobData?: any, + insuranceCardData?: any, errors?: string[] } const BENE_DENIED_ACCESS = "access_denied" const FE_MSG_ACCESS_DENIED = "Beneficiary denied app access to their data" const ERR_QUERY_EOB = "Error when querying the patient's EOB!" +const ERR_QUERY_INSURANCE_CARD = "Error when querying the patient's digital insurance card!" const ERR_MISSING_AUTH_CODE = "Response was missing access code!" const ERR_MISSING_STATE = "State is required when using PKCE" @@ -31,6 +33,7 @@ const loggedInUser: User = { function clearBB2Data() { loggedInUser.authToken = undefined; loggedInUser.eobData = {}; + loggedInUser.insuranceCardData = {}; } // AuthorizationToken holds access grant info: @@ -47,7 +50,7 @@ app.get("/api/authorize/authurl", (req: Request, res: Response) => { // where is space delimited v2 scope specs (url encoded) // e.g. patient/ExplanationOfBenefit.rs const redirectUrl = bb.generateAuthorizeUrl(authData) + - "&scope=patient%2FExplanationOfBenefit.rs" + "&scope=patient%2FExplanationOfBenefit.rs%20patient%2FPatient.rs%20patient%2FCoverage.rs" res.send(redirectUrl); }); @@ -94,6 +97,39 @@ app.get("/api/bluebutton/callback", (req: Request, res: Response) => { console.log("Error data:", e.response.data); } } + + try { + // data flow: call the $generate-digital-insurance-card operation to get the + // beneficiary's CARIN Digital Insurance Card (C4DIC) FHIR bundle. + // This operation is only available on BB2 v3. + const insuranceCardResults = await bb.getInsuranceCardData(authToken); + authToken = insuranceCardResults.token; // in case authToken got refreshed + + loggedInUser.authToken = authToken; + + // the SDK does not throw on HTTP error responses (e.g. missing scope, 404), + // it resolves with the error body in response.data, so check status here + const status = insuranceCardResults.response?.status; + if (status && status >= 200 && status < 300) { + loggedInUser.insuranceCardData = insuranceCardResults.response?.data; + } else { + process.stdout.write(ERR_QUERY_INSURANCE_CARD + '\n'); + process.stdout.write("Insurance card response status: " + String(status) + '\n'); + process.stdout.write( + "Insurance card response data: " + + JSON.stringify(insuranceCardResults.response?.data) + '\n' + ); + loggedInUser.insuranceCardData = {"message": ERR_QUERY_INSURANCE_CARD}; + } + } catch (e: any) { + loggedInUser.insuranceCardData = {"message": ERR_QUERY_INSURANCE_CARD}; + process.stdout.write(ERR_QUERY_INSURANCE_CARD + '\n'); + process.stderr.write("Exception: " + String(e) + '\n'); + if (e.response) { + console.log("Error status:", e.response.status); + console.log("Error data:", e.response.data); + } + } } else { clearBB2Data(); process.stdout.write(ERR_MISSING_AUTH_CODE + '\n'); @@ -135,6 +171,13 @@ app.get("/api/data/benefit", (req: Request, res: Response) => { } }); +// data flow: front end fetch the digital insurance card ($generate-digital-insurance-card) +app.get("/api/data/insurancecard", (req: Request, res: Response) => { + if (loggedInUser.insuranceCardData) { + res.json(loggedInUser.insuranceCardData); + } +}); + const port = 3001; app.listen(port, () => { process.stdout.write(`[server]: Server is running at https://localhost:${port}`);