HL Portal Documentation
Overview
HLC Portals is a multi-portal healthcare platform for Healthy Living Clinic, built with Next.js, Prisma, and MySQL.
The system includes:
- Admin Portal
- EMR Portal
- CRM Portal
- Patient Portal
- Public Checkout + Consultation intake flow
It supports organization-scoped data, subscriptions, order lifecycle management, intake/questionnaires, prescriptions, workflow automations, and payment gateway switching (Stripe/NMI).
Recent updates included in this documentation:
- Checkout template and cart workflow improvements
- Processor-level payment visibility in EMR for Super Admin
- Detailed checkout/payment failure causes returned by API/toasts
- Subscription billing metadata persistence on checkout-created orders
- Subscription switch/payoff implementation plan in
docs/subscription-switching-plan.md - Security email templates, login alerts, welcome email, and email 2FA
- Staff role access to both CRM and EMR portals
Technology Stack
- Frontend: Next.js App Router, React, Ant Design
- Backend/API: Next.js API Routes
- Database: MySQL (via Prisma ORM)
- Auth: JWT-based auth with server-side permission checks
- Payments: Stripe and NMI
- Messaging: SMTP email, Twilio SMS
- Telephony: RingCentral integration (configurable)
Auth and Session Security
The current auth model is hardened beyond the original localStorage token flow.
- Access tokens are validated server-side on protected API requests.
- Refresh tokens are stored in
HttpOnlycookies and in the database. - Refresh tokens are rotated on refresh.
- Logout clears auth cookies and deletes refresh sessions.
- Reset-password and admin session-revoke actions invalidate refresh sessions.
- Client-side persisted auth state keeps only basic user profile info, not raw JWTs.
Important behavior:
- Access tokens are still short-lived JWTs, so a currently issued access token can remain valid until expiry.
- Refresh-session revocation is immediate.
Permission Model
Permissions now work in three layers:
1. Organization-level feature toggles
Stored in:
OrganizationSettings.portalPermissions
Configured by:
- Super Admin in Admin -> Organizations
Purpose:
- defines the feature ceiling for an organization
- hides blocked portal sections in the UI
- blocks the corresponding backend routes
2. Organization custom roles
Stored in:
OrganizationRole.portalPermissions
Configured by:
- Admin -> Users
- available to
SUPER_ADMINandORG_ADMIN
Purpose:
- allows each organization to create extra roles such as Billing, Care Coordinator, or EMR Read Only
- narrows access within the organization-level feature ceiling
- can be assigned to users without changing the built-in system role enum
Built-in system roles still exist:
SUPER_ADMINORG_ADMINCLINICIANSTAFFPATIENT
Custom roles are layered on top of those built-in roles. They do not replace SUPER_ADMIN behavior.
3. User-level feature overrides
Stored in:
User.portalPermissionOverrides
Status:
- backend field is still present for future use
- per-user override UI is intentionally hidden right now
- current admin flow should use org-level toggles plus custom roles instead
Effective permission rule
SUPER_ADMINbypasses all permission toggles- everyone else gets:
- organization permission
- then custom-role permission, if assigned
- then user override, if that layer is re-enabled later
- lower layers can only narrow access
- if the organization disables a feature, the user cannot re-enable it below that ceiling
Current permission keys
admin.users.manageadmin.audit_logs.viewadmin.settings.viewadmin.settings.branding.editadmin.settings.domains.manageadmin.settings.communication.editadmin.settings.payment_gateway.editadmin.settings.security.editcrm.carts.viewcrm.checkout_template.managecrm.templates.managecrm.workflows.manageemr.dashboard.viewemr.patients.viewemr.appointments.viewemr.consultations.viewemr.subscriptions.viewemr.orders.viewemr.processor_logs.viewemr.drugs.viewemr.questionnaires.view
Portal Modules
Admin Portal
- Organization management
- User management
- Global/organization settings
- Payment gateway configuration
- Audit logs
EMR Portal
- Patients, charts, consultations, appointments
- Orders and subscriptions
- Intake review workflow (approve / needs correction / reject)
- Prescriptions and fulfillment flow
- Drugs catalog and product-to-drug mapping
- Processor Logs (Super Admin): cross-org processor event timeline including checkout failures
CRM Portal
- Leads, campaigns, templates, tasks
- Product catalog (store products)
- Workflow engine with triggers, conditions, steps, and run logs
- Checkout template builder/editor
- Security and welcome email template management
Payment Recovery
The platform now supports a first-pass subscription payment recovery flow on top of the existing payment.failed workflow trigger.
Current recovery model
- recurring subscription renewal charges that fail are marked with recovery metadata on the related order
- recovery retries are processed by the existing subscription processor cron
- patient portal shows a payment-recovery warning and sends the patient to billing
- EMR subscriptions page shows a payment recovery queue
Cron requirement
Yes, payment recovery needs the subscription processor cron to keep running.
That processor handles both:
- recurring subscription billing
- payment recovery retries
Use this workflow trigger
payment.failed
For a renewal/dunning workflow, filter it to subscription failures using:
subscription.idorder.subscriptionIdorder.sourceContext
Recovery fields available in workflow payload
On the order object:
order.recoveryStatusorder.retryCountorder.maxRetryCountorder.nextRetryAtorder.lastRetryAtorder.recoveryUrl
At the top level:
paymentRecovery.statuspaymentRecovery.retryCountpaymentRecovery.maxRetryCountpaymentRecovery.nextRetryAtpaymentRecovery.lastRetryAtpaymentRecovery.recoveryUrlpaymentRecovery.autoChargeStatuspaymentRecovery.autoChargeMessage
Recovery status meanings
RETRY_SCHEDULED- the system will retry the original renewal charge on the saved payment method
NEEDS_PAYMENT_METHOD- no usable payment method is available; patient action is required
RESOLVED- the renewal payment recovered successfully
FAILED_FINAL- retry attempts were exhausted
Recovery settings behavior
Configured in:
Admin -> Settings -> Payment Recovery
Fields:
Enable payment recovery notifications- only controls automatic email/SMS sends
- it does not stop retry attempts
Max retry count- total number of recovery retry attempts before the order moves to
FAILED_FINAL
- total number of recovery retry attempts before the order moves to
Retry delays in days- comma-separated schedule, for example
1,3,5 - retry
0uses the first value - retry
1uses the second value - retry
2uses the third value
- comma-separated schedule, for example
Send retry reminder on or after retry count- controls when the configured retry reminder email/SMS is sent
- example:
1= send after the first failed retry2= send after the second failed retry
- if set to
0, it still does not send before retries start 0behaves like “send on the first failed retry” because reminders are only evaluated after a retry attempt happens
Does disabling notifications stop retries?
No.
- retries continue according to the configured retry count and delay schedule
- disabling notifications only stops the automatic email/SMS sends
Missing or expiring cards
Patient portal behavior:
- if a patient has an active subscription and no saved payment method, a warning banner is shown on the dashboard
- if a saved card is expired or within 30 days of expiry, a dashboard banner is shown
Expiry reminders:
- orgs can enable card-expiry reminder email/SMS in
Admin -> Settings -> Payment Recovery - reminder timing is configured with
Send on these days before expiry - use a comma-separated schedule such as
30,7,3,1 - the same configured email template is reused for each matching reminder day
- reminder processing runs from the same subscription processor cron
Example workflow branches
Ask the patient to update card details
- Trigger:
payment.failed - Condition:
paymentRecovery.status equals NEEDS_PAYMENT_METHOD - Action: send email or SMS with billing link
- Action: webhook to external support/CRM if needed
Second or third retry follow-up
- Trigger:
payment.failed - Condition:
paymentRecovery.retryCount greater_than 1 - Action: send a stronger reminder email
Final failure escalation
- Trigger:
payment.failed - Condition:
paymentRecovery.status equals FAILED_FINAL - Action: pause or cancel subscription
- Action: notify staff
Patient recovery URL
Current recovery URL:
/patient/invoices?recovery=1
That page highlights failed renewal recovery, saved cards, and the add-payment-method flow.
Current behavior:
paymentRecovery.recoveryUrlresolves to/patient/invoices?recovery=1- it is currently a fixed system URL, not a custom per-organization URL
Workflow Builder Versioning
The workflow system now supports a safe V1/V2 coexistence path.
Current model
Workflow.builderVersionV1V2
Workflow.blueprint- legacy executable workflow definition
Workflow.blueprintV2- new builder definition storage for V2 workflows
Why this exists
This allows the team to:
- keep existing production workflows on the current V1 builder
- create and edit V2 workflows in parallel
- test V2 structure and UI without replacing the active legacy workflow first
- remove or cut over V1 only after V2 is validated
Current execution behavior
- V1 workflows:
- can be edited in the legacy workflow builder
- can be activated
- are executed by the current workflow engine
- V2 workflows:
- open in the dedicated V2 builder route
- store their definition in
blueprintV2 - are intentionally not executed yet by the live workflow engine
- test execution is intentionally blocked for now
Current UI routing
- legacy builder:
/crm/workflows/[id]
- new builder:
/crm/workflows-v2/[id]
Recommended migration pattern
- Leave the live V1 workflow active.
- Create a new V2 workflow for the same business process.
- Build and validate the V2 definition/UI.
- When the V2 executor is ready and tested, cut over deliberately.
- Only then deactivate/remove the old V1 workflow.
Patient Portal
- Dashboard, profile, appointments, records
- Invoices and payments
- Saved payment methods
- Subscriptions
- Intake form completion/resubmission
Core Business Flow
- Patient checks out from cart/public buy link.
- Payment succeeds.
- Order is created (and subscription if applicable).
- Patient is redirected to consultation intake forms.
- Order moves through questionnaire/review statuses:
AWAITING_REQUIREMENTSPROCESSING(submitted, pending medical review)AWAITING_SCRIPT(approved and ready for Rx)REJECTED(if medically rejected)
- If rejected, linked subscription(s) can be auto-cancelled with reason logged.
- Prescription is sent; fulfillment/shipping statuses continue in EMR.
Payment and Processor Event Behavior
Active Gateway Processing
- Card token/profile can be stored for both Stripe and NMI in checkout flow.
- Charge always runs through the currently active organization gateway setting.
- If active gateway tokenization fails, checkout returns explicit gateway failure details.
Checkout Failure Handling
- On failed checkout payment:
- cart is marked
FAILED cart.failedandpayment.failedworkflow triggers fire- API response returns detailed gateway decline cause when available
- temporary failed order/invoice rows created during checkout are cleaned up to avoid EMR order clutter
- cart is marked
- Because failed order rows are cleaned up, processor failures are still visible via EMR Processor Logs.
Detailed Failure Cause in API/Toast
- Failure messages now include gateway reason text and codes when available.
- Example:
Payment failed: Card Type Verification Error (code: 461/CV). You can retry from this same cart link.
Checkout Flow (With URL Placeholders)
Use this pattern for hosted checkout domains/environments.
1) Entry URL (Buy Link)
- Single or multiple products:
https://<checkout-domain>/buy/<productId>https://<checkout-domain>/buy/<productId1>,<productId2>,<productId3>
Example:
https://checkout.healthyliving.clinic/buy/PRODUCT_A,PRODUCT_B
Behavior:
- System resolves product IDs.
- Creates/loads a cart session.
- Redirects to cart page.
2) Cart URL
https://<checkout-domain>/cart/<cartId>
Example:
https://checkout.healthyliving.clinic/cart/<UNIQUE_CART_ID>
Behavior:
- Shows cart items and pricing.
- Collects patient details (name/email/phone/password) and payment.
- Creates patient account if new.
- Persists unique
cartIdfor abandoned cart / retry workflows.
3) Checkout Submit
- API route (server-side):
POST /api/public/cart/<cartId>/checkout
Behavior on success:
- Payment captured (based on active gateway).
- Order created.
- Subscription created (if product type is subscription).
- Order metadata links subscription IDs when applicable.
- For subscription checkout orders, billing metadata is saved:
billingIntervalbillingIntervalCountcycleNumber(starts at1)billingPeriodStartbillingPeriodEnd
- Cart marked completed.
4) Post-Payment Redirect to Consultation
https://<consultation-domain>/consultation/start/<sessionKey>
Example:
https://consultation.healthyliving.clinic/consultation/start/<SESSION_KEY>
Local testing example:
http://localhost:3200/consultation/start/<SESSION_KEY>
Behavior:
- Loads questionnaires mapped to purchased product(s).
- Patient submits intake forms.
- Status flow begins (
AWAITING_REQUIREMENTS->PROCESSING-> etc.).
5) Completion Redirect to Patient Portal
https://<patient-portal-domain>/patient/dashboard
Local example:
http://localhost:3200/patient/dashboard
Behavior:
- Patient lands in portal after consultation/intake completion.
- Can track invoices, subscriptions, intake status, and next actions.
6) Suggested URL Placeholder Set
- Checkout domain:
https://<checkout-domain> - Consultation domain:
https://<consultation-domain> - Patient portal domain:
https://<patient-portal-domain> - Buy URL:
https://<checkout-domain>/buy/<productIdsCsv> - Cart URL:
https://<checkout-domain>/cart/<cartId> - Consultation URL:
https://<consultation-domain>/consultation/start/<sessionKey> - Portal URL:
https://<patient-portal-domain>/patient/dashboard
7) Key Identifiers in Flow
productId: product identifier from CRM catalogcartId: unique cart identifier (for abandoned/retry workflows)sessionKey: consultation session key used to fetch assigned questionnairesorderId: created order ID in EMR/CRM datasubscriptionId/subscriptionIds: linked subscription records for recurring products
Embeddable Cart Drawer
Use this when you want to place a lightweight "Add to Cart" or "Start Checkout" button on an external website and have HLC create the cart in the backend.
Backend Endpoint
- Public cart-create API:
POST /api/public/cart/create
Request body:
{
"productIds": ["96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"],
"checkoutTemplateId": "default"
}Accepted fields:
productIds: array of product IDs or CSV stringcheckoutTemplateId: optional checkout template IDtemp: optional alias for template IDsourcePath: optional tracking pathsourceDomain: optional tracking domain
Response includes:
cartIdcheckoutUrlitemssubtotaldiscounttaxtotalpaymentGateway
Embed Script
Static script location:
/embed/hlc-cart-drawer.js
Source/edit file:
scripts/hlc-cart-drawer.source.js
Build command after edits:
npm run embed:buildThis script:
- calls the public cart-create endpoint
- can render a cart icon into a mount element
- can open a right-side drawer immediately or only when cart icon is clicked
- renders cart items, quantity, pricing, and remove actions
- links the user into the real HLC cart/checkout page
Localhost Test
Use this first while local app is running on http://localhost:3200:
<div id="portals-cart-app"></div>
<script>
window.HLCCartDrawerConfig = {
apiBase: "http://localhost:3200",
cartMountSelector: "#portals-cart-app",
openOnAdd: true,
title: "Your Cart",
checkoutLabel: "Continue to Checkout",
accentColor: "#1a3c5e"
};
</script>
<script src="http://localhost:3200/embed/hlc-cart-drawer.js"></script>
<button data-hlc-product-ids="96d4d3eb-88f1-4d7b-9d85-73b34b3745f1">
Add to Cart
</button>Production / Dev Host Usage
Replace apiBase with your actual HLC environment:
<div id="portals-cart-app"></div>
<script>
window.HLCCartDrawerConfig = {
apiBase: "https://dev.healthyliving.clinic",
cartMountSelector: "#portals-cart-app",
openOnAdd: true,
title: "Your Cart",
checkoutLabel: "Continue to Checkout",
accentColor: "#1a3c5e"
};
</script>
<script src="https://dev.healthyliving.clinic/embed/hlc-cart-drawer.js"></script>
<button data-hlc-product-ids="96d4d3eb-88f1-4d7b-9d85-73b34b3745f1">
Add to Cart
</button>Add Silently, Open From Cart Icon
Use this when clicking Add to Cart should only update the cart badge and not open the drawer immediately.
<div id="portals-cart-app"></div>
<script>
window.HLCCartDrawerConfig = {
apiBase: "https://dev.healthyliving.clinic",
cartMountSelector: "#portals-cart-app",
openOnAdd: false,
title: "Your Cart",
checkoutLabel: "Continue to Checkout",
accentColor: "#1a3c5e"
};
</script>
<script src="https://dev.healthyliving.clinic/embed/hlc-cart-drawer.js"></script>
<button
data-hlc-product-ids="96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"
data-hlc-open-on-add="false">
Add to Cart
</button>Behavior:
- item is added to current cart
- badge count increases on cart icon
- clicking cart icon opens drawer
- clicking the same product button again does not increase quantity; it opens the existing drawer/cart instead
Multiple Products
<button
data-hlc-product-ids="prod-id-1,prod-id-2"
data-hlc-template-id="tpl-test">
Start Checkout
</button>Direct JavaScript API
<script>
HLCCartDrawer.open({
apiBase: "http://localhost:3200",
productIds: ["96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"],
checkoutTemplateId: "default"
});
</script>Add without opening:
<script>
HLCCartDrawer.add({
apiBase: "https://dev.healthyliving.clinic",
productIds: ["96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"],
openOnAdd: false
});
</script>Data Attributes Supported
data-hlc-product-idsdata-hlc-template-iddata-hlc-titledata-hlc-checkout-labeldata-hlc-accentdata-hlc-open-on-add
Config Options
apiBasecartMountSelectoropenOnAddbuttonSelectortitlecheckoutLabelaccentColor
Current Scope
- Drawer shows created cart contents and quantity returned from backend
- Existing embed cart is reused and new items are merged into the same cart
- Clicking an already-added product button again does not increment quantity; it reopens the current cart drawer
- Cart badge count is stored in browser local storage and refreshed from HLC cart API
- Product image URLs are normalized against HLC
apiBase - Removing an item from the drawer updates the backend cart and real checkout page
- Removing the last item deletes the cart from backend, clears stored cart state, and prevents abandoned-cart follow-up on an empty cart
- Checkout button always links to the real HLC cart page
- CORS is enabled on
/api/public/cart/createfor external-site usage - Quantity increment/decrement inside the drawer is not implemented yet
Order Statuses
Configured order statuses include:
AWAITING_FULFILLMENTAWAITING_PAYMENTAWAITING_REQUIREMENTSAWAITING_REQUISITIONAWAITING_RESULTSAWAITING_SCRIPTAWAITING_SHIPMENTINCOMPLETEPENDINGPROCESSINGREJECTEDRETURNEDSHIPPEDCOMPLETEDCANCELLEDREFUNDED
Roles and Portal Access
Roles in active use
SUPER_ADMINORG_ADMINCLINICIANSTAFFPATIENT
Portal access by role
SUPER_ADMIN: Admin + CRM + EMR + cross-org contextORG_ADMIN: Admin + CRM + EMR (org-scoped)CLINICIAN: EMRSTAFF: CRM + EMRPATIENT: Patient Portal only
Staff role behavior
- Staff can log in normally.
- Staff can access:
/crm/*/emr/*
- Staff does not get Admin Portal access.
- CRM/EMR sidebar portal switching shows:
CRMEMR
Login landing behavior
Login redirect still uses the user record's portal field as the default landing portal:
ADMIN->/admin/dashboardCRM->/crm/dashboardEMR->/emr/dashboardPATIENT->/patient/dashboard
This means a STAFF user can have default landing on CRM while still being allowed to switch to EMR.
EMR Processor Logs (Super Admin)
Location:
- EMR left navigation:
Processor Logs
Purpose:
- Unified processor event view across checkout and portal-originated payment events.
- Includes events even when checkout-failed temporary orders are cleaned up.
What is shown:
- Event type (
TOKENIZATION_FAILED,CUSTOMER_PROFILE_LINKED,PAYMENT_SUCCEEDED,PAYMENT_FAILED,PAYMENT_REFUNDED) - Gateway (
STRIPE/NMI) - Organization, patient, order/cart/invoice references
- Transaction/charge/customer(vault) identifiers
- Processor response codes and gateway message text
Scope behavior:
- Page is available to
SUPER_ADMINonly. - It follows global org context selector from EMR header (
org_context) for filtering.
Cart Statuses
OPEN: Cart created and still in progress.FAILED: Checkout payment attempt failed (cart can be retried from same link).COMPLETED: Payment succeeded and checkout finalized.ABANDONED: Cart was idle past configured timeout and marked by cart processor job.
Repository Structure (High Level)
app/: Next.js pages and API routesapp/admin/*app/emr/*app/crm/*app/patient/*app/api/*(all server routes)
lib/: Shared server/client utilities- Auth, payments, workflows, subscription billing, audit, encryption
prisma/: Prisma schema and seed scriptscripts/: Data import and PHI rotation scriptsstore/: Zustand auth store
Security and Authentication
Current security features
- JWT access + refresh auth
- Login audit logs
- Login alert emails
- Suspicious/new-device login alert emails
- Failed login tracking
- Account lockout after configurable failed attempts
- Email-based 2FA
- Welcome email on new user creation
- Forgot password + reset password flow
Global security controls
Configured in Admin Settings:
- Login alert on every login
- Suspicious login alert
- Max failed login attempts
- Lockout duration (minutes)
- Email 2FA enable/disable
- Admin-only 2FA scope
Per-user security controls
Configured in Admin -> Users:
- Enable/disable per-user login alerts
- Force 2FA
- Exempt user from org-wide 2FA
2FA behavior
- If login requires 2FA:
- password is verified
- 6-digit code is emailed
- user submits code on login page
- session is finalized and dashboard redirect happens
Meaning of settings:
Email 2FA: enables org-wide email 2FAAdmin-only 2FA: when enabled, org-wide 2FA applies only to non-patient/admin-like users
If Email 2FA is off, Admin-only 2FA alone does nothing.
Security email templates
Built-in protected templates include:
Security: Login AlertSecurity: Suspicious Login AlertSecurity: Forgot PasswordSecurity: Password Reset SuccessSecurity: Failed Login WarningSecurity: Account LockedSecurity: 2FA CodeWelcome: New User
Behavior:
- protected templates are not deletable
- default content can be restored
- test email can be sent from template editor
- security templates use HTML mode to preserve email-safe HTML markup
Merge tags commonly available in security emails
{{organization.name}}{{organization.slug}}{{organization.email}}{USER.FIRSTNAME}{USER.LASTNAME}{USER.EMAIL}{USER.PORTAL}{{portalLabel}}
Event-specific examples:
{{otpCode}}{{resetUrl}}{{expiryMinutes}}{{remainingAttempts}}{{lockedUntil}}{{ipAddress}}{USERAGENT}{{loginUrl}}
SMTP behavior
- security/welcome/template test emails use organization SMTP settings from DB first
- env SMTP values act as fallback
- template-level
From Name/From Emailoverrides are supported - if template email is blank, org SMTP sender is used
Welcome email behavior
- Welcome email is sent when a user is newly created:
- from Admin -> Users
- from checkout/patient account creation
- guarded by audit log so it sends once per user creation event
Webhook Response Data In Later Steps
Webhook steps (http_post) do not only send data. Their response is available to later workflow steps.
After a webhook step runs, later conditions/actions can use:
httpPost.okhttpPost.statushttpPost.requesthttpPost.response
Examples:
-
condition on webhook success:
- field:
httpPost.ok - operator:
equals - value:
true
- field:
-
condition on decision returned by webhook:
- field:
httpPost.response.decision - operator:
equals - value:
approve
- field:
-
condition on eligibility returned by webhook:
- field:
httpPost.response.eligible - operator:
equals - value:
true
- field:
If multiple webhook steps exist in the same workflow:
httpPost.*refers to the latest webhook step output- use
stepResults.<stepId>when you need a specific earlier webhook result
Example Intake Flow
Example workflow:
- Trigger:
intake.completed - Action:
http_post- send full payload with
{{payload}}
- send full payload with
- Condition:
httpPost.response.decision equals approve
- If true:
- continue next step
- If false:
update_order_statusupdate_subscription_status- send email/SMS/webhook
Organization Scope
- Core data is organization-scoped.
- Super Admin can access cross-organization context.
- Org Admin/clinical roles should operate within assigned organization scope.
HeyMarket SMS Integration
HeyMarket is supported as an alternative SMS provider alongside Twilio.
Configuration
Admin Settings → Communication → SMS Provider:
- Toggle:
Twilio|HeyMarket - HeyMarket fields: API ID, API Secret, Inbox ID, Creator ID
How it works
- Setting
smsProvider = "heymarket"inOrganizationSettingsroutes all outbound SMS through HeyMarket - JWT auth:
apiId||apiSecretas HS256 signing key, payload{iss: apiId, iat: now} - JWT is generated server-side using Node.js
crypto.createHmac(no external library) - Endpoint:
POST https://api.heymarket.com/v1/message/send
Fallback order
- HeyMarket (if
smsProvider = "heymarket"and credentials configured) - Twilio DB credentials (if configured in org settings)
- Twilio env vars (
TWILIO_*) as final fallback
Files
lib/workflow-engine.ts—sendSms(),buildHeyMarketJwt(),sendSmsViaHeyMarket()lib/payment-recovery-notify.ts— HeyMarket-aware SMS on payment recoverylib/payment-method-expiry-notify.ts— HeyMarket-aware SMS on card expiryapp/admin/settings/page.tsx— HeyMarket settings UI tab
Order Tracking
When an order status is SHIPPED, AWAITING_SHIPMENT, COMPLETED, or RETURNED, a Shipping & Tracking section is shown in the order detail modal.
Fields
| Field | Description |
|---|---|
carrier | Carrier name (e.g. UPS, FedEx, USPS) |
trackingNumber | Carrier tracking number |
trackingUrl | Full tracking URL |
shippedAt | Timestamp — auto-set when status transitions to SHIPPED |
Behavior
- Fields are editable inline in the EMR order modal
- "Save Tracking" saves without changing order status
shippedAtis auto-populated when status is first set toSHIPPED
License
Proprietary software for Healthy Living Clinic. Unauthorized use, distribution, or modification is prohibited unless explicitly approved.
Updated 9 minutes ago