Today started with a bug report that led down a rabbit hole. Cases with freecard numbers (frikort - Swedish healthcare free cards) were being registered incorrectly in Carasent. What seemed like a small flow issue turned out to be a fundamental misunderstanding of the business logic, plus an entirely missing invoice lifecycle.
The client's patient portal integrates with Carasent/Webdoc for healthcare journaling. When a practitioner closes a case, we need to create the proper documentation in Carasent - either a simple note or a full visit with booking, registration, and billing.
The Original Problem
Cases with freecard numbers were going through the note-only flow instead of creating proper visits. The billing department noticed the discrepancy when reconciling invoices.
The existing flow decision logic was checking three conditions to determine when to use the simplified note flow: if the diagnosis was Z71.9, if it was Z76.0, or if the patient had a freecard number. This seemed reasonable at first glance - freecard patients don't pay, so why bother with the invoice flow? That assumption was completely wrong.
The business requirement: freecard patients still need proper visit documentation. The freecard simply means the invoice gets marked as paid differently - the visit itself is identical. Clinical documentation requirements don't change based on payment method.
Tracing the Flow
The sign-and-close endpoint orchestrates everything. Here's the full flow for a normal (non-note) case:
The TypeScript Type Issue
Before even fixing the flow logic, there was a prerequisite bug. The freeCardNumber field wasn't defined in the UserCase TypeScript type. The type included all the standard case fields like caseId, userId, conditionId, practitionerId, timestamps, description, and closure status - but freeCardNumber was completely absent.
Without this field in the type definition, the frontend was silently dropping the freecard value. The type system should have caught this - but the backend was returning loosely typed responses. A reminder that loose types hide bugs. Adding the freeCardNumber as an optional string field to the UserCase type was the first step.
Simplifying the Flow Decision
The corrected flow decision logic is embarrassingly simple. The original version checked for Z71.9, Z76.0, and then had complex conditional logic involving freecard status and other diagnosis checks. The fixed version only checks two things: is it Z71.9 or is it Z76.0? That's it. The freecard status is completely removed from the flow decision.
Z71.9 is "Health advice" - no visit needed, just documentation. Z76.0 is "Repeat prescription" - same deal. Everything else, regardless of payment method, requires the full visit flow.
Implementing the Invoice Functions
The Carasent library was missing invoice functionality entirely. Two new functions were needed.
The first function creates an invoice for a visit. It takes an authentication token, clinic ID, visit ID, and an optional total payment amount defaulting to 100 SEK. The function validates that a visit ID is provided, then calls the Carasent API's invoice endpoint with a POST request containing the visit ID and payment amount. One quirk discovered during implementation: the API returns an array even when creating a single invoice. The function needed to handle both array and single object responses, extracting the first element when an array is returned. If no invoice ID is present in the response, the function throws an error.
The second function marks an invoice as paid. It takes the authentication token, clinic ID, invoice ID, payment amount, and a payment method code. Payment method "4" corresponds to "Paid invoice" in Carasent's system. The function sends a PATCH request to update the invoice with paid status set to true along with the payment details.
The Freecard Edge Case
Here's where it gets interesting. Marking an invoice as paid can fail for freecard patients - they might have freecard status registered directly in Carasent that our system doesn't know about. The invoice marking becomes a best-effort operation.
The implementation wraps the payment marking call in a try-catch block. If an invoice ID exists, it attempts to mark it as paid. On success, everything proceeds normally. On failure, instead of throwing an error and aborting the entire flow, the code logs a warning indicating the failure may be due to freecard status and continues execution. The key insight: invoice payment failure shouldn't abort the entire case closure.
The visit documentation is complete. The billing discrepancy can be handled administratively. Crashing the flow helps nobody.
Hardcoding patientTypeId
While debugging, another issue surfaced. The patientTypeId was being pulled from environment variables, requiring a lookup function to resolve the patient type name to an ID. This added unnecessary complexity.
Patient type "2" is the only type we use. The environment variable lookup provided no benefit - if the type ever changes, we'd need code changes anyway. Environment variables are for secrets and environment-specific values, not constants. The fix was straightforward: replace the dynamic lookup with a hardcoded value of 2.
This change was replicated in the utskick/send route too - anywhere we touched Carasent needed the same simplification.
The Log Trail
After fixes, the logs tell the complete story. Each step is numbered and logged: getting the patient, getting the booking, registering the visit, creating the invoice, creating the journal entry, signing the record, marking the invoice as paid, and finally closing the case in the database. Each successful step is marked with a checkmark in the logs.
When something fails, the log shows exactly where. The numbered steps and explicit success markers make debugging trivial. The extra console.log calls pay for themselves on the first production issue.
Verifying the Fix
The client exported their Carasent visits for January 1st through 7th. Before today's fix, cases with freecard numbers showed up as notes instead of visits. After deploying the fix, a database query confirmed that cases closed post-deployment now have proper visit IDs in Carasent. The query selected cases closed within the date range, looking at the case ID, description, closure timestamp, and freecard number. The billing department confirmed invoices are appearing correctly.
Files Modified
Five files were modified to implement these fixes. The types file had the freeCardNumber field added to the UserCase type. The CloseCaseModal component had its flow logic corrected to remove the freecard condition. The sign-and-close API route received the full invoice flow implementation. The utskick send route had its patientTypeId hardcoded. And the Carasent library gained the two new invoice functions.
Lessons
First, business logic beats clever code. The original conditional tried to optimize for payment scenarios. The business requirement was simpler: diagnosis determines flow, payment is separate.
Second, type definitions are documentation. The missing freeCardNumber field was a silent failure. TypeScript only helps when types are complete.
Third, APIs lie. The invoice endpoint returns an array. The documentation implied otherwise. Always handle both cases.
Fourth, graceful degradation matters. Invoice marking can fail for legitimate reasons. Warn and continue instead of blocking the entire flow.
Fifth, log everything. Numbered steps with explicit success markers make debugging trivial. The extra console.log calls pay for themselves on the first production issue.
The fix was deployed by afternoon.