Skip to content

feat(paykit): add custom plan and meter intervals#118

Open
t3duk wants to merge 7 commits intogetpaykit:mainfrom
t3duk:t3duk/feat/custom-intervals
Open

feat(paykit): add custom plan and meter intervals#118
t3duk wants to merge 7 commits intogetpaykit:mainfrom
t3duk:t3duk/feat/custom-intervals

Conversation

@t3duk
Copy link
Copy Markdown
Contributor

@t3duk t3duk commented Apr 12, 2026

This PR addresses the limited intervals allowed on a plan price and feature limit reset.

Summary

  • extend plan billing intervals to support day, week, month, quarterly, biyear, and year while mapping provider-safe variants to Stripe recurring interval counts, may be a problem if other providers don't support it
  • extend metered feature resets to support biweek, quarterly, biyear, and arbitrary positive integer second intervals through a shared interval utility
  • update sync logic and tests so new interval values serialize and validate consistently

Todo

  • update docs to reflect changes

Let me know if any of my implementation looks wrong, or if we don't want to move in this direction.

Summary by CodeRabbit

  • New Features

    • Support for additional billing intervals: day, week, quarterly, biyear; metered resets also accept biweek and numeric-second values.
  • Improvements

    • Consistent interval handling across products, subscriptions and entitlements for accurate reset scheduling.
    • Price formatting updated to show interval suffixes (e.g. "/day", "/wk", "/3 mo", "/6 mo").
  • Tests

    • Added tests for new intervals, numeric-second resets, and provider interval mappings.
  • Documentation

    • Docs updated with examples and guidance for new intervals and numeric resets.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 12, 2026

@t3duk is attempting to deploy a commit to the maxktz Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

Introduced a centralized types/interval module with schemas and utilities; replaced local interval/date arithmetic with shared addInterval/getSecondInterval; extended supported plan and metered reset intervals (day, week, biweek, quarterly, biyear, numeric seconds); wired serialization and Stripe recurrence mapping across services and docs.

Changes

Cohort / File(s) Summary
Interval Module & Tests
packages/paykit/src/types/interval.ts, packages/paykit/src/types/__tests__/interval.test.ts
Added a new interval module exporting plan/metered schemas, types, and utilities (addInterval, getSecondInterval, serializeMeteredResetInterval, getStripeRecurringInterval) and tests validating new interval values and behaviors.
Schema & Type Wiring
packages/paykit/src/types/schema.ts, packages/paykit/src/types/product.ts
Replaced inline enums with planIntervalSchema and meteredResetIntervalSchema; updated exported type aliases to use PlanInterval/MeteredResetInterval.
Entitlement & Subscription Logic
packages/paykit/src/entitlement/entitlement.service.ts, packages/paykit/src/subscription/subscription.service.ts
Removed local UTC date-arithmetic helpers; use addInterval and getSecondInterval for computing/advancing next reset times and handling fixed-second intervals.
Product Persistence & Sync
packages/paykit/src/product/product.service.ts, packages/paykit/src/product/product-sync.service.ts
Serialize metered resetInterval via serializeMeteredResetInterval when persisting and normalize it during feature-diffing in sync to avoid false positives.
Pricing Formatting & Stripe Integration
packages/paykit/src/cli/utils/format.ts, packages/paykit/src/providers/stripe.ts
Extended formatPrice to render day, week, quarterly, biyear; Stripe price creation now derives recurring.interval and optional recurring.count via getStripeRecurringInterval.
Documentation & Examples
landing/content/docs/**
landing/content/docs/concepts/entitlements.mdx, .../plans-and-features.mdx, .../flows/metered-usage.mdx, .../flows/subscription-billing.mdx, .../get-started/installation.mdx
Updated docs and examples to list new named intervals (day, week, biweek, quarterly, biyear, month, year) and numeric-second resets; added example plans (team, burst) demonstrating weekly and quarterly pricing and numeric reset usage.
Minor Formatting/Display
packages/paykit/src/cli/utils/format.ts
Formatting logic extended to cover additional interval labels (/day, /wk, /3 mo, /6 mo).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 I hopped through intervals, small and grand,

day, week, biweek — stitched by gentle hand.
Quarterly moons and biyear springs,
seconds counted like tiny things.
A rabbit cheers: the timelines stand.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(paykit): add custom plan and meter intervals' accurately summarizes the main change: extending plan and metered reset intervals with new named options and custom second-based intervals.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@t3duk
Copy link
Copy Markdown
Contributor Author

t3duk commented Apr 12, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/paykit/src/types/interval.ts (1)

103-116: Keep Stripe translation next to the Stripe adapter.

The rest of this module is provider-agnostic, but getStripeRecurringInterval bakes Stripe semantics into src/types. Moving this helper beside packages/paykit/src/providers/stripe.ts will make additional providers easier to support without growing the core interval module around Stripe-specific behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/paykit/src/types/interval.ts` around lines 103 - 116, The helper
getStripeRecurringInterval embeds Stripe-specific semantics inside the
provider-agnostic interval module; move this function out of
packages/paykit/src/types/interval.ts into the Stripe adapter file (where the
Stripe provider is implemented, e.g., next to the Stripe adapter in
packages/paykit/src/providers/stripe.ts) and update imports: remove
getStripeRecurringInterval from the interval module, export it or define it
locally in the Stripe adapter (keeping the same signature) and adjust any call
sites to import it from the Stripe provider module instead so core types remain
provider-agnostic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/paykit/src/entitlement/entitlement.service.ts`:
- Around line 35-36: The current catch-up loop that repeatedly calls
addInterval(nextResetAt, resetInterval) until nextResetAt > now is O(N) for
small second-based resetInterval; replace it with an O(1) fast-forward: compute
the number of missed intervals as Math.max(0, Math.ceil((now - nextResetAt) /
resetInterval)) (guarding that resetInterval > 0) and advance nextResetAt by
missedIntervals * resetInterval in one step instead of looping; update the code
paths that reference nextResetAt and addInterval (the while loop and any
subsequent logic in the entitlement reset handling) to use this computed
fast-forwarded nextResetAt.

In `@packages/paykit/src/types/interval.ts`:
- Around line 15-18: The schema currently only accepts numeric seconds but your
serializer (serializeMeteredResetInterval) emits numeric intervals as strings,
so validate both forms by updating meteredResetIntervalSchema to accept either
the enum values or a coerced/parsed positive integer; for example replace the
z.number().int().positive(...) branch with z.coerce.number().int().positive(...)
(or use z.string().regex(/^\d+$/).transform(s=>parseInt(s,10))) so a serialized
"90" will validate and round-trip against meteredResetIntervalSchema while
keeping meteredResetIntervalValues unchanged.

---

Nitpick comments:
In `@packages/paykit/src/types/interval.ts`:
- Around line 103-116: The helper getStripeRecurringInterval embeds
Stripe-specific semantics inside the provider-agnostic interval module; move
this function out of packages/paykit/src/types/interval.ts into the Stripe
adapter file (where the Stripe provider is implemented, e.g., next to the Stripe
adapter in packages/paykit/src/providers/stripe.ts) and update imports: remove
getStripeRecurringInterval from the interval module, export it or define it
locally in the Stripe adapter (keeping the same signature) and adjust any call
sites to import it from the Stripe provider module instead so core types remain
provider-agnostic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4c457a1e-0296-4163-a4d0-8ebec58e2c00

📥 Commits

Reviewing files that changed from the base of the PR and between 95d7147 and acfab82.

📒 Files selected for processing (10)
  • packages/paykit/src/cli/utils/format.ts
  • packages/paykit/src/entitlement/entitlement.service.ts
  • packages/paykit/src/product/product-sync.service.ts
  • packages/paykit/src/product/product.service.ts
  • packages/paykit/src/providers/stripe.ts
  • packages/paykit/src/subscription/subscription.service.ts
  • packages/paykit/src/types/__tests__/interval.test.ts
  • packages/paykit/src/types/interval.ts
  • packages/paykit/src/types/product.ts
  • packages/paykit/src/types/schema.ts

Comment thread packages/paykit/src/entitlement/entitlement.service.ts
Comment thread packages/paykit/src/types/interval.ts
t3duk added 2 commits April 12, 2026 20:41
for stuff like months, years etc, it isn't fixed, and will vary,
therfore we use the while loop. but for fixed intervals we can use a
calculation which is o(1) to fast track this
@t3duk
Copy link
Copy Markdown
Contributor Author

t3duk commented Apr 12, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@t3duk t3duk marked this pull request as ready for review April 12, 2026 20:00
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
landing/content/docs/flows/metered-usage.mdx (1)

60-60: Include the burst example in the explanatory sentence for consistency.

The paragraph mentions only Free/Pro after adding burst, which can feel inconsistent with the snippet.

✏️ Suggested docs tweak
-Free customers get 100 messages per month; Pro customers get 2,000. Custom second-based resets are useful for short-lived buckets like hourly quotas, while named resets cover common billing cadences such as `biweek` or `quarterly`.
+Free customers get 100 messages per month; Pro customers get 2,000. The Burst add-on shows a short reset window (`3_600` seconds) for hourly quotas, while named resets cover common cadences such as `biweek` or `quarterly`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@landing/content/docs/flows/metered-usage.mdx` at line 60, Update the
explanatory sentence that currently lists only Free and Pro customers to also
mention the newly added "burst" plan for consistency with the snippet; find the
paragraph that begins "Free customers get 100 messages per month; Pro customers
get 2,000." and change it to include "burst" (and its quota) alongside Free and
Pro, and ensure the sentence still explains that custom second-based resets are
for short-lived buckets and named resets cover cadences like `biweek` or
`quarterly`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@landing/content/docs/concepts/plans-and-features.mdx`:
- Line 133: The bullet for `interval` in plans-and-features.mdx is a fragment;
change it to a complete sentence such as "The `interval` can be `day`, `week`,
`month`, `quarterly`, `biyear`, or `year`." Locate the existing line referencing
`interval` and replace the fragment with this full-sentence wording so the docs
read clearly and consistently.

---

Nitpick comments:
In `@landing/content/docs/flows/metered-usage.mdx`:
- Line 60: Update the explanatory sentence that currently lists only Free and
Pro customers to also mention the newly added "burst" plan for consistency with
the snippet; find the paragraph that begins "Free customers get 100 messages per
month; Pro customers get 2,000." and change it to include "burst" (and its
quota) alongside Free and Pro, and ensure the sentence still explains that
custom second-based resets are for short-lived buckets and named resets cover
cadences like `biweek` or `quarterly`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 07c96f62-5125-4cf3-925f-9cccc518116d

📥 Commits

Reviewing files that changed from the base of the PR and between 044923d and d08edb7.

📒 Files selected for processing (5)
  • landing/content/docs/concepts/entitlements.mdx
  • landing/content/docs/concepts/plans-and-features.mdx
  • landing/content/docs/flows/metered-usage.mdx
  • landing/content/docs/flows/subscription-billing.mdx
  • landing/content/docs/get-started/installation.mdx
✅ Files skipped from review due to trivial changes (2)
  • landing/content/docs/get-started/installation.mdx
  • landing/content/docs/concepts/entitlements.mdx

Comment thread landing/content/docs/concepts/plans-and-features.mdx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant