@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 +2 -2
- package/package.json +3 -3
- package/src/config.ts +0 -35
- package/src/definition.tsx +1 -22
- package/src/entitlement.ts +0 -1
- package/src/handlers.ts +0 -2
- package/src/index.ts +0 -6
- package/src/plans.ts +0 -6
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/
|
|
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.
|
|
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.
|
|
24
|
-
"@meith/theme-kit": "^0.
|
|
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>>,
|
package/src/definition.tsx
CHANGED
|
@@ -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.
|
|
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()
|
package/src/entitlement.ts
CHANGED
|
@@ -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')
|