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}`);