{"record":{"id":"a790d2a0ceed08ad","repo":"Stirling-Tools/Stirling-PDF","slug":"failed-to-update-seat-count-error-message","errorCode":null,"errorMessage":"Failed to update seat count: ${error.message}","messagePattern":"Failed to update seat count: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/proprietary/services/licenseService.ts","lineNumber":546,"sourceCode":"      throw new Error(\n        \"Supabase is not configured. Seat updates are not available.\",\n      );\n    }\n\n    const baseUrl = window.location.origin;\n    const returnUrl = `${baseUrl}/settings/adminPlan?seats_updated=true`;\n\n    const { data, error } = await supabase.functions.invoke(\"manage-billing\", {\n      body: {\n        return_url: returnUrl,\n        license_key: licenseKey,\n        self_hosted: true,\n        new_seat_count: newSeatCount,\n      },\n    });\n\n    if (error) {\n      throw new Error(`Failed to update seat count: ${error.message}`);\n    }\n\n    if (!data || !data.url) {\n      throw new Error(\"No billing portal URL returned\");\n    }\n\n    return data.url;\n  },\n};\n\n/**\n * Map license type to plan tier\n * @param licenseInfo - Current license information\n * @returns Plan tier: 'free' | 'server' | 'enterprise'\n */\nexport const mapLicenseToTier = (\n  licenseInfo: LicenseInfo | null,\n): \"free\" | \"server\" | \"enterprise\" | null => {","sourceCodeStart":528,"sourceCodeEnd":564,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/proprietary/services/licenseService.ts#L528-L564","documentation":"Thrown by updateEnterpriseSeats when supabase.functions.invoke('manage-billing', {body:{new_seat_count,...}}) returns a non-null error. The manage-billing function creates a billing-portal session reflecting the requested seat change; a returned error means the function failed — invalid license key, the new seat count violates Stripe's minimums, no matching subscription, or missing STRIPE_SECRET_KEY. error.message is wrapped.","triggerScenarios":"new_seat_count below the subscription's minimum seats; license_key invalid/revoked; subscription not found in Stripe; manage-billing function missing secrets; proration rejected by Stripe.","commonSituations":"Admin tries to reduce seats below the active-user count; license was cancelled; Stripe product configured without per-seat pricing metadata; edge function not deployed.","solutions":["Read error.message — Stripe errors surface the constraint (e.g. minimum seats).","Validate new_seat_count against the current seat minimum before calling.","Confirm manage-billing is deployed with STRIPE_SECRET_KEY.","Re-verify the license key is still active before retrying."],"exampleFix":"// before\nconst url = await licenseService.updateEnterpriseSeats(newCount, key);\n\n// after\ntry {\n  const url = await licenseService.updateEnterpriseSeats(newCount, key);\n  window.location.href = url;\n} catch (e) {\n  showToast(e instanceof Error ? e.message : 'Seat update failed');\n}","handlingStrategy":"try-catch","validationCode":"if (!Number.isInteger(newSeatCount) || newSeatCount < currentMinSeats) {\n  setError(`Seat count must be at least ${currentMinSeats}`);\n  return;\n}\nif (!licenseKey) { setError('License key required'); return; }","typeGuard":"export function isViableSeatCount(n: number, min: number): boolean {\n  return Number.isInteger(n) && n >= min;\n}","tryCatchPattern":"try {\n  const url = await licenseService.updateEnterpriseSeats(newCount, key);\n  window.location.href = url;\n} catch (e) {\n  showToast(e instanceof Error ? e.message : 'Seat update failed');\n}","preventionTips":["Validate new_seat_count against the subscription's minimum before calling.","Confirm manage-billing is deployed with STRIPE_SECRET_KEY.","Re-verify the license key is active before retrying."],"tags":["supabase","edge-function","stripe","license","enterprise","seats","proprietary","typescript"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}