Billing & Usage — Remaining Tasks
This document lists only the remaining work for Phase 5 of AppiFire AI Chat (Option A). The full Phase 5 plan is in billing-and-usage.md.
Status: Most of Phase 5 is implemented (plans, billing routes, subscription confirm, buy-credits, webhooks, Prisma models, usage dashboard, limit enforcement, cancel flow, one-time reconcile + balance repair, subscription included credits per currentPeriodEnd, success banners). See Credits wallet: one-time & subscription for the authoritative credits write-up. The items below are remaining gaps and follow-ups.
1. Monthly included credits on subscription (implemented)
Section titled “1. Monthly included credits on subscription (implemented)”Done. Included subscription credits are driven by PAID_PLAN_INCLUDED_CREDITS_USD in .env (via app/lib/plans.server.js). Grants run from:
app/routes/app.billing.confirm.jsx(after ACTIVE subscription +currentPeriodEnd)webhooks.app_subscriptions.update.jsxwhen ACTIVE andcurrent_period_endis presentapp/routes/app.billing.jsxloader (uses ShopifyactiveSubscriptions[0].currentPeriodEndif webhooks lag)
Idempotency: shops.last_paid_included_credits_period_end matches AppSubscription.currentPeriodEnd so each billing period is credited at most once.
After lapse: shops.suppress_included_credits_after_subscription_lapse is set when the shop leaves paid; no further included grants even after resubscribe. See Credits wallet: one-time & subscription.
2. Billing UI: show “Total replies this month” for paid users
Section titled “2. Billing UI: show “Total replies this month” for paid users”Current behaviour: For paid users, the Billing page shows AI credit balance and the “Usage stats (last 30 days)” section (API calls, OpenRouter cost, charged cost, tokens, last 10 questions). It does not show a simple “Total replies this month” for the current calendar month.
Required: For paid users, display total replies this month (same metric as for free: assistant messages in the current calendar month) in the “Current usage this month” or “Subscription” section.
Implementation: The loader already computes repliesUsed (current month). In the Billing page UI, when currentPlan === "paid", add a line such as: “Replies this month: {repliesUsed}” (and optionally “Each reply uses a variable amount of credits.”) so paid merchants see both credit balance and reply count.
Acceptance: Paid users see their current-month reply count on the Billing page alongside credit balance and usage stats.
3. Store subscriptionLineItemId when easy (optional)
Section titled “3. Store subscriptionLineItemId when easy (optional)”Current behaviour: Shop.subscriptionLineItemId exists in the schema and is cleared on cancel, but it is never set when the subscription is created or updated.
Required: If the Shopify API readily returns a subscription line item ID when confirming or fetching the subscription, store it in shop.subscriptionLineItemId. If it requires extra round-trips or non-standard handling, leave this as a future enhancement and do not change logic elsewhere.
Implementation: In app/routes/app.billing.confirm.jsx, when you fetch the subscription via node(id: AppSubscription/...), check whether the GraphQL response includes a line-item identifier (e.g. lineItems { id } or similar on AppSubscription). If yes, set subscriptionLineItemId on the shop update. If the Admin API does not expose it in the same query you already use, skip this task.
Acceptance: When the API response includes a subscription line item ID, it is stored on the shop; otherwise behaviour is unchanged.
4. Low-balance email: separate infrastructure task (Phase 5.1)
Section titled “4. Low-balance email: separate infrastructure task (Phase 5.1)”Current behaviour: Low-balance alert logic is implemented: thresholds, once-per-day throttling (lastLowBalanceAlertAt), and free vs paid (remaining replies vs remaining balance) are correct. sendLowBalanceAlert in app/lib/email.server.js is a stub (e.g. logs only; no real email sent).
Required: Treat “send real email when low-balance alert fires” as a separate, future task (e.g. Phase 5.1 or “Email infrastructure”), not as a blocker for Phase 5 completion. No change to Phase 5 scope.
Implementation: When you are ready, wire sendLowBalanceAlert to your chosen provider (e.g. Resend, SendGrid) and configure credentials (env vars). No changes to the remaining-tasks list for Phase 5.
Acceptance: Phase 5 is considered complete without real email delivery; real email is a later infrastructure task.
Daily spend limit: Not implemented. Paid plan allows a reply whenever creditBalanceCents > 0. The last reply may bring the balance below $0 (soft cap); no daily spend cap is enforced.
5. Plan definitions in the database
Section titled “5. Plan definitions in the database”Current behaviour: Plan names, prices, and caps (e.g. $20/mo, 50 free replies, $10 included credits) are defined in code in app/lib/plans.server.js.
Required: Implement plan definitions in the database so pricing and caps can be changed without a code deploy. Plans (Free, Paid) must be stored in the DB and used by billing and limits.
Implementation approach:
- Add a
Plan(orplans) table in Prisma, e.g.:id,name,monthlyPriceCents,includedReplies(nullable),creditsIncludedCents,isActive,createdAt,updatedAt. - Seed Free and Paid rows (e.g. via migration or seed script).
- Update
app/lib/plans.server.js(or replace with a server module) to read plans from the DB instead of a static object — e.g.getPlans(),getPlanById(id). - Update the Billing page loader and any code that uses
PLANSto use the DB-backed plans (with fallback or cache as needed). - Update
app/lib/limits.server.jsto use the free plan’sincludedRepliesfrom the DB (or keep a single “free reply cap” config if only one free plan exists).
Acceptance: Plan names, monthly price, free reply cap, and included credits are stored in the DB and used by billing and limits; changes do not require a code deploy.
6. Make plan constants configurable (environment variables)
Section titled “6. Make plan constants configurable (environment variables)”Current behaviour: Monthly price (e.g. $20) and free reply cap (e.g. 50) are hard-coded in app/lib/plans.server.js.
Required: Make paid plan monthly price and free plan reply cap configurable via environment variables so they can be changed without a code deploy.
Implementation approach:
- Add
PAID_PLAN_MONTHLY_PRICE,FREE_PLAN_REPLIES_CAP, and optionallyPAID_PLAN_CREDITS_INCLUDED_CENTSto.env(e.g.PAID_PLAN_MONTHLY_PRICE=20,FREE_PLAN_REPLIES_CAP=50,PAID_PLAN_CREDITS_INCLUDED_CENTS=1000). - In
app/lib/plans.server.js, read these withparseInt(process.env.…, 10)and use fallbacks to current values (e.g. 20, 50, 1000) when env is missing or invalid. - Export
PLANS(or a getter) so billing and limits use the same values.
Acceptance: Changing the monthly price (e.g. from $20 to another value) or the free reply cap (e.g. from 50 to another value) does not require a code change; it is done via env vars in .env.
7. Behaviour already agreed (no code change)
Section titled “7. Behaviour already agreed (no code change)”- Paid plan credit check: Keep soft behaviour: allow a reply whenever
creditBalanceCents > 0; deduct actual cost up to the current balance (so the last reply can consume the remaining cents). No strict “balance ≥ estimated cost” check required.
Checklist — Remaining tasks only
Section titled “Checklist — Remaining tasks only”- Monthly included credits: Implemented with
PAID_PLAN_INCLUDED_CREDITS_USD,last_paid_included_credits_period_end, confirm + webhook + billing loader; suppressed after subscription lapse (suppress_included_credits_after_subscription_lapse). See credits-wallet-one-time-and-subscription.md. - Paid UI — replies this month: On Billing page, for paid users, show “Total replies this month” (use existing
repliesUsedfrom loader). - subscriptionLineItemId (if easy): When confirming subscription, if Shopify API returns a line item ID in the subscription payload/query, set
shop.subscriptionLineItemId; otherwise leave as-is. - Low-balance email: Left as separate infrastructure task (Phase 5.1); Phase 5 complete without it.
- Plan definitions in DB: Add
Plantable, seed Free/Paid, and have billing + limits read plans from DB so pricing/caps can change without deploy. - Configurable plan constants: Make paid monthly price and free reply cap configurable via env vars (e.g.
PAID_PLAN_MONTHLY_PRICE,FREE_PLAN_REPLIES_CAP) so $20 and 50 (or other values) can be changed without code deploy.
Next: launch-and-publishing-guide.md (when moving to Phase 6).