@meith/plugin-dues 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ name**.
10
10
  What a paying member gets is whatever the group carries: forum access, a
11
11
  badge, a name colour. Dues never makes a permission decision — it decides who
12
12
  is in a group and until when, through the board's own
13
- [timed-grant capability](../../docs/plugin-api.md#timed-group-grants), and
13
+ [timed-grant capability](../../docs/customization/plugins.md#timed-group-grants), and
14
14
  every guarantee that capability makes applies here. The load-bearing one:
15
15
  **a grant always expires on its own.** If this plugin is removed, its tasks
16
16
  stop, or Stripe closes the account, every sold membership drains away at its
@@ -33,7 +33,7 @@ answers for the board is not.
33
33
  **Try it before you read any of this**: [demo.meith.dev](https://demo.meith.dev)
34
34
  runs this plugin against a Stripe that is not Stripe — a shop with a year of
35
35
  history behind it, and a checkout a visitor can actually go through. See
36
- [demo mode](../../docs/demo-mode.md#the-shop-and-a-stripe-that-is-not-stripe).
36
+ [demo mode](../../docs/guides/operations/demo-mode.md#the-shop-and-a-stripe-that-is-not-stripe).
37
37
 
38
38
  ## Setting up a board
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meith/plugin-dues",
3
- "version": "0.21.1",
3
+ "version": "0.22.0",
4
4
  "description": "Membership dues for a Meith board: Stripe-backed subscriptions as a contained plugin.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,8 +20,8 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "@meith/plugin-kit": "^0.21.1",
24
- "@meith/theme-kit": "^0.21.1"
23
+ "@meith/plugin-kit": "^0.22.0",
24
+ "@meith/theme-kit": "^0.22.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": "^19.2.0"
package/src/config.ts CHANGED
@@ -14,12 +14,6 @@ export interface DuesPlanInput {
14
14
  readonly hidden?: boolean
15
15
  }
16
16
 
17
- /**
18
- * What a board still configures in code, when it registers the plugin with
19
- * arguments rather than taking the zero-argument export. `currency` and
20
- * `graceDays` moved to plugin settings — see `resolveDuesConfig` — because a
21
- * marketplace install can only supply a key, never a constructor argument.
22
- */
23
17
  export interface DuesConfigInput {
24
18
  readonly label?: string
25
19
  readonly plans?: readonly DuesPlanInput[]
@@ -39,14 +33,12 @@ export interface DuesPlan {
39
33
  readonly hidden: boolean
40
34
  }
41
35
 
42
- /** The half of the configuration fixed at plugin registration. */
43
36
  export interface DuesStaticConfig {
44
37
  readonly label: string
45
38
  readonly seedPlans: readonly DuesPlan[]
46
39
  readonly extraRedirectHosts: readonly string[]
47
40
  }
48
41
 
49
- /** The static half plus the settings an operator edits in the panel. */
50
42
  export interface DuesConfig extends DuesStaticConfig {
51
43
  readonly currency: string
52
44
  readonly graceDays: number
@@ -60,13 +52,6 @@ export const DEFAULT_GRACE_DAYS = 7
60
52
  export const MIN_GRACE_DAYS = 0
61
53
  export const MAX_GRACE_DAYS = 30
62
54
 
63
- /**
64
- * The board-wide currency is a `select` setting, and a select needs a fixed
65
- * list of options — unlike a plan's own currency (still any ISO 4217 code,
66
- * typed into the plan form), this is the curated set an operator picks a
67
- * default from. Labels are bare codes, on purpose: they need no translation
68
- * and cost nothing in the message catalog.
69
- */
70
55
  export const DUES_CURRENCY_OPTIONS: readonly { readonly value: string; readonly label: string }[] =
71
56
  [
72
57
  { value: 'usd', label: 'USD' },
@@ -94,16 +79,6 @@ function refuse(message: string): never {
94
79
  throw new Error(`dues plugin configuration: ${message}`)
95
80
  }
96
81
 
97
- /**
98
- * Validates the code-configured half of the plugin — a plan's `currency` is
99
- * still supplied per-plan on the admin form, but a code-declared *seed* plan
100
- * has none of its own, so it always seeds under whatever the `currency`
101
- * setting resolves to when the board's first request seeds it. That means
102
- * the period-plus-grace cap below cannot know the actual `graceDays` in
103
- * force (a setting, resolved per request) — it checks against the worst
104
- * case, `MAX_GRACE_DAYS`, so no seed can exceed the board's two-year grant
105
- * cap no matter how the setting is later changed.
106
- */
107
82
  export function parseDuesConfig(input: DuesConfigInput = {}): DuesStaticConfig {
108
83
  const label = (input.label ?? 'Membership').trim()
109
84
  if (label === '') refuse('label must not be empty.')
@@ -195,16 +170,6 @@ function clampGraceDays(value: number): number {
195
170
  return Math.min(MAX_GRACE_DAYS, Math.max(MIN_GRACE_DAYS, Math.round(value)))
196
171
  }
197
172
 
198
- /**
199
- * Merges the static, code-declared half of the configuration with the two
200
- * settings an operator edits in the panel. Settings have no refusal path —
201
- * unlike `parseDuesConfig`, a bad value here is repaired rather than thrown:
202
- * an unrecognised currency falls back to the default, and an out-of-range
203
- * grace period is clamped to 0–30 days. `resolvePluginSettings` already
204
- * guarantees a `select` setting's stored value is one of its declared
205
- * options, so the currency check below is a second, cheap line of defence
206
- * rather than the one this depends on.
207
- */
208
173
  export function resolveDuesConfig(
209
174
  staticConfig: DuesStaticConfig,
210
175
  settings: Readonly<Record<string, string | number | boolean>>,
@@ -41,20 +41,9 @@ import { runReconcile, runSweep } from './tasks'
41
41
  import { CodesPage, LedgerPage, MembersPage, PlansAdminPage, StatusPage } from './ui/admin'
42
42
  import { GoPage, ManagePage, PlansPage, ReturnPage } from './ui/pages'
43
43
 
44
- /**
45
- * The code-configured path: still takes constructor arguments, for a board
46
- * that registers Dues directly in `community.plugins.ts` rather than
47
- * through a marketplace install. `dues`, below, is this called with none —
48
- * the zero-argument export a marketplace install actually uses.
49
- */
50
44
  export function createDues(input: DuesConfigInput = {}): PluginDefinition {
51
45
  const staticConfig = parseDuesConfig(input)
52
46
 
53
- /**
54
- * Resolved fresh per call: `currency` and `graceDays` are settings, so a
55
- * request made after an operator edits them must see the new value, not
56
- * one baked in when the plugin was registered.
57
- */
58
47
  const configFor = (context: PluginRuntimeContext): DuesConfig =>
59
48
  resolveDuesConfig(staticConfig, context.settings)
60
49
 
@@ -68,7 +57,7 @@ export function createDues(input: DuesConfigInput = {}): PluginDefinition {
68
57
  return definePlugin({
69
58
  key: 'dues',
70
59
  name: 'Dues',
71
- version: '0.21.1',
60
+ version: '0.22.0',
72
61
  description: en['dues.definition.description'].replace(
73
62
  '{label}',
74
63
  staticConfig.label.toLowerCase(),
@@ -370,14 +359,4 @@ export function createDues(input: DuesConfigInput = {}): PluginDefinition {
370
359
  })
371
360
  }
372
361
 
373
- /**
374
- * The zero-argument export: what a marketplace install actually registers.
375
- * `allowedRedirectHosts` carries only Stripe's own hosts — a marketplace
376
- * install cannot express a constructor argument, and `allowedRedirectHosts`
377
- * is fixed on the definition at this call, before any settings resolve, so
378
- * there is no later point where a board-configured host could be added. A
379
- * board that genuinely needs another redirect host (a proxy, a loopback
380
- * address for a test double) registers `createDues({ extraRedirectHosts })`
381
- * directly instead.
382
- */
383
362
  export const dues: PluginDefinition = createDues()
@@ -198,7 +198,6 @@ function settledPeriodEnd(
198
198
  return addBillingInterval(now, plan?.billingInterval ?? 'month')
199
199
  }
200
200
 
201
- /** @see plugins/dues/README.md#changing-and-retiring-one */
202
201
  const FALLBACK_PERIOD = { years: 0, months: 0, weeks: 0, days: 30 }
203
202
 
204
203
  function parseStoredPeriod(order: OrderRow) {
package/src/handlers.ts CHANGED
@@ -72,7 +72,6 @@ export function entitlementDeps(services: DuesServices): EntitlementDeps {
72
72
 
73
73
  const LOOPBACK_HOSTNAMES = new Set(['127.0.0.1', 'localhost', '[::1]'])
74
74
 
75
- /** docs/membership-guide.md#origin-for-stripe-redirects */
76
75
  function isLoopbackHost(host: string): boolean {
77
76
  let parsed: URL
78
77
  try {
@@ -83,7 +82,6 @@ function isLoopbackHost(host: string): boolean {
83
82
  return LOOPBACK_HOSTNAMES.has(parsed.hostname.toLowerCase())
84
83
  }
85
84
 
86
- /** docs/membership-guide.md#origin-for-stripe-redirects */
87
85
  export function requestOrigin(request: PluginRequest): string {
88
86
  if (request.boardUrl !== '') return request.boardUrl
89
87
  const host = request.headers.host ?? ''
package/src/index.ts CHANGED
@@ -1,9 +1,3 @@
1
- /**
2
- * `plugin` and `messages` (below) are the manifest-installable convention
3
- * board.plugins.json generation relies on (scripts/board-plugins-gen.mjs) —
4
- * re-exports, not a second definition, so `dues` stays the name everyone
5
- * reads in code that names it directly.
6
- */
7
1
  export type { DuesConfigInput, DuesPlanInput } from './config'
8
2
  export { createDues, dues, dues as plugin } from './definition'
9
3
  export {
package/src/plans.ts CHANGED
@@ -117,12 +117,6 @@ function bad(error: string): PlanParse {
117
117
  return { ok: false, error }
118
118
  }
119
119
 
120
- /**
121
- * Checks a fixed plan's length against `MAX_GRACE_DAYS`, not the live
122
- * `grace_days` setting — mirroring the seed-path cap in `parseDuesConfig`
123
- * (`config.ts`), for the same reason: `grace_days` is a runtime setting an
124
- * operator can raise after the plan already exists.
125
- */
126
120
  export function parsePlanForm(form: PlanFormInput): PlanParse {
127
121
  const key = (form.key ?? '').trim().toLowerCase()
128
122
  if (!PLAN_KEY.test(key)) return bad('bad-key')