Skip to content

feat(payments): add the subscription data model and plan settings - #849

Merged
cameri merged 5 commits into
cameri:mainfrom
Priyanshubhartistm:feat/subscription-data-model
Oct 18, 2026
Merged

cameri merged 5 commits into
cameri:mainfrom
Priyanshubhartistm:feat/subscription-data-model

Conversation

@Priyanshubhartistm

Copy link
Copy Markdown
Collaborator

Description

First, behaviour-neutral step towards recurring membership plans (design: #673). It adds the storage and settings the later PRs build on; nothing reads them yet, so with no plans configured the relay behaves exactly as before.

  • Migration: a user_subscriptions table (one row per pubkey: plan id, status active / renewal_pending / past_due / expired, current period start and end, grace end, cancel-at-period-end) and plan_id, period_start, period_end columns on invoices. The subscription is keyed by pubkey, so there is no separate subscription_id column. current_period_end is NULL for a subscription that never ends.
  • Grandfathering: the migration gives every user who is already admitted a never-ending subscription on a reserved grandfathered plan, so turning plans on later cannot lock anyone out.
  • Settings: payments.subscriptionPlans[] (id, amount in msats, intervalSeconds, graceSeconds, optional description and per-plan limits), empty by default, documented in CONFIGURATION.md and validated in validateSettings: ids are unique and not grandfathered, amounts are positive integers and unique (the amount paid will select the plan), intervals positive, grace non-negative.
  • Repositories: UserSubscriptionRepository (find by pubkey, upsert, and the three lookups the renewal/expiry sweeps need: due for renewal, period ended, grace ended); InvoiceRepository and the Invoice type carry the plan and period, which an existing invoice never has rewritten.
  • .knip.json ignores the new repository until a later PR uses it.
  • Unit tests for the repositories, invoice mapping and plan validation. I also ran the migration up, down and up again against a PostgreSQL 16 database seeded with admitted and non-admitted users, and exercised the repositories against it.

Related Issue

Closes #848

Motivation and Context

Admission today is a one-time boolean, so nothing records a paid period, a plan, or when access ends. Recurring billing and tiered access (#673) need that state first, and landing it on its own keeps the follow-up PRs small to review.

@changeset-bot

changeset-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8c8d6da

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
nostream Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Critical impact] The PR appears safe to merge; no actionable new issues remain.

Summary

Adds subscription storage and plan settings without enabling recurring billing.

  • Each member can have a stored plan and subscription period.
  • Operators can define validated plans for recurring memberships.
  • Invoices can carry the plan and period they pay for.

Diagram

%%{init: {'theme': 'neutral'}}%%
erDiagram
  users ||--o| user_subscriptions : has
  user_subscriptions {
    binary pubkey PK,FK
    text plan_id
    enum status
    timestamp current_period_start
    timestamp current_period_end
    timestamp grace_ends_at
    boolean cancel_at_period_end
  }
Loading

Reviews (2) · Last reviewed commit: "Merge branch 'main' into feat/subscripti..." · Reviewed by Greptile

Comment thread src/utils/settings-config.ts Outdated
Comment thread migrations/20261016_120000_create_user_subscriptions_table.js Outdated
Signed-off-by: Priyanshubhartistm <bhartipriyanshustm@gmail.com>
Signed-off-by: Priyanshubhartistm <bhartipriyanshustm@gmail.com>
Signed-off-by: Priyanshubhartistm <bhartipriyanshustm@gmail.com>
@Priyanshubhartistm
Priyanshubhartistm force-pushed the feat/subscription-data-model branch from 3ade6a8 to c3883f4 Compare October 18, 2026 12:07
@coveralls

coveralls commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 39155021027

Warning

No base build found for commit b2b45c2 on main.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 74.827%

Details

  • Patch coverage: 72 of 72 lines across 2 files are fully covered (100%).

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 10662
Covered Lines: 8358
Line Coverage: 78.39%
Relevant Branches: 5566
Covered Branches: 3785
Branch Coverage: 68.0%
Branches in Coverage %: Yes
Coverage Strength: 846.95 hits per line

💛 - Coveralls

@cameri
cameri merged commit 7e504e1 into cameri:main Oct 18, 2026
16 checks passed
@cameri

cameri commented Oct 18, 2026

Copy link
Copy Markdown
Owner

thank you @Priyanshubhartistm

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.

feat(payments): subscription data model and plan settings

3 participants