import { useEffect, useRef, useState } from 'react'; import type { PractitionerRole } from 'fhir/r4'; import type { ClinicianSeatSave } from '../../hooks/useClinicianSeatCreate'; import { useOptionalBillingContext } from '../../services/BillingContext'; import { useOptionalStaffPermissionServiceContext } from '../../services/StaffPermissionService'; import { useAuthServiceContext } from '../../services/AuthService'; import { usePractitionerRoleServiceContext } from '../../services/PractitionerRoleService'; import { useHttpService } from '../../services/HttpService'; import { startAddOnCheckout } from '../../lib/billingCheckout'; import { clinicianSeatScopeDigest } from '../../utils/clinicianSeatCreate'; import { scopeDigest } from '../../utils/messaging/sendRecovery'; import { claimSavedClinicianCheckout } from '../../utils/savedClinicianCheckout'; import { setClinicianEnabled } from '../../utils/clinicianEnabled'; import { describeClinicianSeatCharge } from '../../utils/clinicianSeatCharge'; import { describeWriteFailure } from '../../utils/fetchFailureMessage'; import { ClinicianSeatConfirmDialog } from './ClinicianSeatField'; import { viewerEpoch } from '../../utils/writeThroughHold'; import { StripeCheckoutModal } from '../../pages/SubscriptionPage/StripeCheckoutModal'; export interface SavedClinicianSeatActionsProps { roleId: string; /** The editor's full viewer/practice/subject/opening identity, independent of one Check press. */ ownerKey: string; beginSave(): ClinicianSeatSave; onRole(role: PractitionerRole, owner: ClinicianSeatSave): void | Promise; disabled?: boolean; } type Billing = NonNullable>; export const SavedClinicianSeatActions = (props: SavedClinicianSeatActionsProps) => { const billing = useOptionalBillingContext(); const permissions = useOptionalStaffPermissionServiceContext(); const auth = useAuthServiceContext(); if (!billing || !permissions?.canManage('clinicians')) return null; const isAdmin = permissions.activeWorkspace === auth.USER_ROLES.ADMIN; if (billing.billingStatus === null || billing.clinicianCount === undefined || billing.addOnPricing?.unitPriceCents?.clinicianSeats === undefined || billing.billingOwner.viewerId !== auth.currentUser?.fhir?.id) return null; if (billing.billingStatus === 'free_basic' && billing.clinicianCount > 0 && !isAdmin) return null; return ; }; const SavedSeatAction = (props: SavedClinicianSeatActionsProps & { billing: Billing }) => { const { checkClinicianSeat, updatePractitionerRole } = usePractitionerRoleServiceContext(); const { post } = useHttpService(); const current = useRef(props); current.current = props; const live = useRef(); const mounted = useRef(true); const [checkoutVisible, setCheckoutVisible] = useState(true); const [busy, setBusy] = useState(false); const [message, setMessage] = useState(); const [paid, setPaid] = useState(); const [checkoutRequested, setCheckoutRequested] = useState(); const [prepared, setPrepared] = useState<{ owner: ClinicianSeatSave; role: PractitionerRole; money: string }>(); const [checkout, setCheckout] = useState<{ owner: ClinicianSeatSave; secret: string }>(); useEffect(() => { mounted.current = true; return () => { mounted.current = false; live.current?.retire(); }; }, []); const moneyNow = () => JSON.stringify([current.current.roleId, current.current.billing.billingOwner, current.current.billing.billingStatus, current.current.billing.clinicianCount, current.current.billing.addOnPricing?.unitPriceCents?.clinicianSeats]); const scopeKey = (owner: ClinicianSeatSave) => JSON.stringify([props.roleId, owner.clientScope]); const charge = describeClinicianSeatCharge({ enabledClinicians: props.billing.clinicianCount, seatPriceCents: props.billing.addOnPricing?.unitPriceCents?.clinicianSeats, hasSubscription: props.billing.billingStatus !== 'free_basic' }); const owned = (owner: ClinicianSeatSave) => { if (!mounted.current) throw new Error('This saved-role action is no longer open.'); owner.admit(); if (current.current.roleId !== props.roleId || current.current.billing.billingOwner.practiceId !== owner.clientScope.organizationId || current.current.billing.billingOwner.actingAsId !== null) throw new Error('Reopen this saved role in its practice before enabling it.'); }; const prepare = async () => { if (!mounted.current) return; live.current?.retire(); const owner = props.beginSave(); live.current = owner; try { owned(owner); setBusy(true); setPrepared(undefined); setMessage(undefined); const result = await owner.step(() => checkClinicianSeat(props.roleId, { transport: owner.transport, organizationId: owner.clientScope.organizationId, practitionerId: owner.clientScope.practitionerId })); owned(owner); if (result.standing === 'enabled' && result.role) { await owner.step(async () => { await props.onRole(result.role!, owner); }); owned(owner); setMessage('This saved role already has an enabled clinician seat.'); return; } if (result.standing !== 'disabled' || !result.role?.meta?.versionId) throw new Error('Check this saved role again before enabling it.'); if ((paid === scopeKey(owner) || checkoutRequested === scopeKey(owner)) && charge.startsSubscription) { setMessage('The checkout result is not confirmed here. Check billing before another purchase or enabling this saved role.'); return; } if (result.role.id !== props.roleId || result.role.organization?.reference !== `Organization/${owner.clientScope.organizationId}` || result.role.practitioner?.reference !== `Practitioner/${owner.clientScope.practitionerId}`) throw new Error('The saved role does not match this staff record.'); setPrepared({ owner, role: structuredClone(result.role), money: moneyNow() }); } catch (error: unknown) { if (owner.belongs()) setMessage(describeWriteFailure(error, 'Could not check this saved role')); } finally { if (owner.belongs()) setBusy(false); } }; const confirm = async () => { if (!prepared) return; const { owner, role, money } = prepared; try { owned(owner); if (money !== moneyNow()) throw new Error('The billing details changed. Check this saved role and confirm the current price again.'); setBusy(true); setPrepared(undefined); const transport = { ...owner.transport, admit: () => { owned(owner); if (money !== moneyNow()) throw new Error('The billing details changed. Confirm the current price again.'); } }; if (charge.startsSubscription) { setCheckoutRequested(scopeKey(owner)); const scope = await owner.step(() => clinicianSeatScopeDigest(owner.clientScope)); const receipt = await owner.step(() => scopeDigest(['saved-clinician-checkout-v1', scope, props.roleId])); transport.admit(); const navigation = performance.getEntriesByType?.('navigation')[0] as PerformanceNavigationTiming | undefined; claimSavedClinicianCheckout(window.sessionStorage, receipt, navigation?.type !== 'reload'); const result = await owner.step(() => startAddOnCheckout((url: string, data: unknown) => { owned(owner); if (money !== moneyNow()) throw new Error('The billing details changed. Confirm the current price again.'); return post(url, data, { ...transport, organizationId: owner.clientScope.organizationId }); }, 'clinicianSeats', props.billing.addOnPricing?.unitPriceCents), true); owned(owner); if (result.clientSecret) { setCheckout({ owner, secret: result.clientSecret }); setCheckoutVisible(true); } else if (result.updated) setMessage('The subscription change was accepted. Check the saved role and enable it separately.'); else throw new Error('The payment form is unavailable. Check billing before trying again.'); } else { const result = await owner.step(() => updatePractitionerRole(setClinicianEnabled({ ...role, active: true }, true), { transport, organizationId: owner.clientScope.organizationId, expectedVersion: role.meta!.versionId, expectIncludedSeat: charge.chargeCents === 0, }), true); owned(owner); await owner.step(async () => { await props.onRole(result, owner); }); owned(owner); setMessage('The saved role has an enabled clinician seat.'); } } catch (error: unknown) { if (owner.belongs()) setMessage(describeWriteFailure(error, 'The result is not confirmed. Check the saved role before trying again.')); } finally { if (owner.belongs()) setBusy(false); owner.retire(); } }; const paymentComplete = async () => { if (!mounted.current || !checkout?.owner.isCurrentScope()) return; setPaid(scopeKey(checkout.owner)); setCheckout(undefined); setMessage('Payment completed. Check the saved role, then explicitly enable it when billing is confirmed.'); const owner = props.beginSave(); live.current = owner; try { owned(owner); await owner.step(() => props.billing.refreshBilling({ signal: owner.transport.signal, deadline: Date.now() + 30_000 })); owned(owner); } catch { /* Payment remains distinct from subscription and seat confirmation. */ } finally { owner.retire(); } }; return <> {checkout?.owner.isCurrentScope() && !checkoutVisible && } {message &&

{message}

} { if (!open) { prepared?.owner.retire(); setPrepared(undefined); } }} onConfirm={() => { void confirm(); }} /> {checkout?.owner.isCurrentScope() && checkoutVisible && { setCheckoutVisible(false); }} onPaymentComplete={() => { void paymentComplete(); }} />} ; };