@meith/plugin-dues 0.7.0 → 0.9.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.
@@ -0,0 +1,263 @@
1
+ {
2
+ "dues.admin.codes.actions": "Actions",
3
+ "dues.admin.codes.any": "any",
4
+ "dues.admin.codes.anyPlan": "Any plan",
5
+ "dues.admin.codes.badCode": "Codes are 3 to 32 letters, digits and hyphens. Leave the box empty to have one invented.",
6
+ "dues.admin.codes.badExpiry": "The expiry needs to be a date in the future, like 2027-01-31.",
7
+ "dues.admin.codes.badMax": "The redemption cap is a whole number, at least 1 — or empty for no cap.",
8
+ "dues.admin.codes.badPercent": "The discount is a whole number from 1 to 100 percent.",
9
+ "dues.admin.codes.badPlan": "That plan does not exist on this board.",
10
+ "dues.admin.codes.code": "Code",
11
+ "dues.admin.codes.codeHelp": "Code — leave empty to have one invented",
12
+ "dues.admin.codes.created": "The code is live: {code} — hand it out however you like. It is shown in full below whenever you need it again.",
13
+ "dues.admin.codes.duplicate": "A code by that name already exists. Every code is unique, spelt any case.",
14
+ "dues.admin.codes.empty": "None yet.",
15
+ "dues.admin.codes.every": "Every code",
16
+ "dues.admin.codes.expired": "expired",
17
+ "dues.admin.codes.expires": "Expires",
18
+ "dues.admin.codes.expiresHelp": "Expires at end of day (UTC) — empty for never",
19
+ "dues.admin.codes.intro": "A code takes a percentage off at checkout: the whole price of a pass, the first payment of a subscription — renewals bill in full. A 100% code on a pass skips Stripe entirely, which is how you comp somebody.",
20
+ "dues.admin.codes.live": "live",
21
+ "dues.admin.codes.mint": "Mint a code",
22
+ "dues.admin.codes.mintButton": "Mint the code",
23
+ "dues.admin.codes.never": "never",
24
+ "dues.admin.codes.noSuch": "That code could not be found.",
25
+ "dues.admin.codes.off": "Off",
26
+ "dues.admin.codes.percentHelp": "Percent off, 1–100",
27
+ "dues.admin.codes.plan": "Plan",
28
+ "dues.admin.codes.planHelp": "Which plan it works on",
29
+ "dues.admin.codes.redeemed": "Redeemed",
30
+ "dues.admin.codes.redemptionCap": "Redemption cap — empty for unlimited",
31
+ "dues.admin.codes.redemptions": "Redemptions count when a payment settles, not when a checkout starts. A code is never deleted — switching it off keeps the history of what it sold.",
32
+ "dues.admin.codes.state": "State",
33
+ "dues.admin.codes.switchOff": "Switch off",
34
+ "dues.admin.codes.switchOn": "Switch on",
35
+ "dues.admin.codes.switchedOff": "switched off",
36
+ "dues.admin.codes.toggledOff": "{code} is switched off. Anyone typing it now is told the code is not usable.",
37
+ "dues.admin.codes.toggledOn": "{code} is back on.",
38
+ "dues.admin.codes.usedUp": "used up",
39
+ "dues.admin.ledger.appendOnly": "Append-only, written as money moves: charges positive, refunds and chargebacks negative. Stripe’s dashboard is the authority; this is the board’s own copy in {currency}.",
40
+ "dues.admin.ledger.byMonth": "By month",
41
+ "dues.admin.ledger.charges": "Charges",
42
+ "dues.admin.ledger.empty": "Empty.",
43
+ "dues.admin.ledger.gross": "Gross",
44
+ "dues.admin.ledger.latest": "Latest entries",
45
+ "dues.admin.ledger.month": "Month",
46
+ "dues.admin.ledger.noMoney": "No money has moved yet.",
47
+ "dues.admin.ledger.refunded": "Refunded",
48
+ "dues.admin.members.actions": "Actions",
49
+ "dues.admin.members.active": "active",
50
+ "dues.admin.members.badDays": "Extensions are 1 to 366 days.",
51
+ "dues.admin.members.cancel": "Cancel renewal",
52
+ "dues.admin.members.cancelled": "Renewal cancelled. They keep what they paid for until the period ends.",
53
+ "dues.admin.members.clear": "Clear the flag",
54
+ "dues.admin.members.cleared": "Flag cleared.",
55
+ "dues.admin.members.closing": "closing",
56
+ "dues.admin.members.description": "Every membership this plugin has sold, flagged rows first. Extending is a grant, not a charge; revoking removes access on the spot without touching the money — refunds happen in Stripe’s dashboard, and the refund webhook revokes on its own.",
57
+ "dues.admin.members.expired": "expired",
58
+ "dues.admin.members.extend": "Extend",
59
+ "dues.admin.members.extendAria": "Days to extend membership {id} by",
60
+ "dues.admin.members.extended": "Membership extended. The member holds their group until the new date.",
61
+ "dues.admin.members.forGood": "for good",
62
+ "dues.admin.members.grace": "grace",
63
+ "dues.admin.members.graceUntil": "Grace until",
64
+ "dues.admin.members.grantRefused": "The extension was recorded, but the board refused the group grant — the row is flagged with the reason.",
65
+ "dues.admin.members.member": "Member",
66
+ "dues.admin.members.noMembership": "That membership is not live any more, so there is nothing to act on.",
67
+ "dues.admin.members.noRenewal": "Only a live subscription has a renewal to cancel.",
68
+ "dues.admin.members.none": "Nothing sold yet.",
69
+ "dues.admin.members.periodEnds": "Period ends",
70
+ "dues.admin.members.plan": "Plan",
71
+ "dues.admin.members.revoke": "Revoke now",
72
+ "dues.admin.members.revoked": "Membership revoked. Their access is gone as of now.",
73
+ "dues.admin.members.revokedStatus": "revoked",
74
+ "dues.admin.members.status": "Status",
75
+ "dues.admin.members.stripeError": "Stripe could not be reached. Nothing changed — try again shortly.",
76
+ "dues.admin.members.stripeUnconfigured": "Stripe is not configured, so the subscription cannot be reached.",
77
+ "dues.admin.members.subscription": "Subscription",
78
+ "dues.admin.plans.add": "Add a plan",
79
+ "dues.admin.plans.addIntro": "A pass is one payment for a fixed stretch — a day to two years. A subscription renews by itself until cancelled. Lifetime is one payment, forever. The key is the plan’s permanent name in records and cannot be changed later; everything else can.",
80
+ "dues.admin.plans.archived": "{key} is off sale. Everyone who holds it keeps it — archiving stops new purchases, nothing else.",
81
+ "dues.admin.plans.autoGift": "A subscription cannot be giftable — it would bill the buyer forever.",
82
+ "dues.admin.plans.badCurrency": "The currency is a three-letter ISO code, like gbp, usd or eur.",
83
+ "dues.admin.plans.badGroup": "That is not a valid group key.",
84
+ "dues.admin.plans.badInterval": "A subscription bills every month or every year.",
85
+ "dues.admin.plans.badKey": "Plan keys are lower-case letters, digits and hyphens, like day-pass.",
86
+ "dues.admin.plans.badLength": "The length is a whole number of days, weeks, months or years — at least 1.",
87
+ "dues.admin.plans.badMode": "Pick how the plan bills.",
88
+ "dues.admin.plans.badName": "The plan needs a name members will read.",
89
+ "dues.admin.plans.badPrice": "The price is a positive whole number of minor units — 500 is £5.00.",
90
+ "dues.admin.plans.badStripePrice": "A pasted Stripe price id starts with price_.",
91
+ "dues.admin.plans.billing": "Billing against {price}",
92
+ "dues.admin.plans.bills": "How it bills",
93
+ "dues.admin.plans.canGift": "Can be bought for another member",
94
+ "dues.admin.plans.created": "The plan {key} is on sale from this moment.",
95
+ "dues.admin.plans.currency": "Currency",
96
+ "dues.admin.plans.days": "days",
97
+ "dues.admin.plans.description": "Description — optional",
98
+ "dues.admin.plans.duplicate": "A plan with that key already exists. Keys are forever; pick another.",
99
+ "dues.admin.plans.edit": "Edit this plan",
100
+ "dues.admin.plans.editing": "Every purchase snapshots its plan — the name, the price, the currency, the length — so editing here never rewrites what anyone already bought. A subscription price change mints a new Stripe price: running subscriptions keep billing what they signed up for, and only the next buyer sees the new number.",
101
+ "dues.admin.plans.fixed": "a pass — one payment, fixed length",
102
+ "dues.admin.plans.footer": "Pass length and billing interval apply to the mode that uses them; the others ignore them. A subscription needs Stripe configured, or a pasted price id.",
103
+ "dues.admin.plans.giftGroup": "Group it grants (marked plugin-grantable under Admin → Groups)",
104
+ "dues.admin.plans.hidden": "Hidden from the shop",
105
+ "dues.admin.plans.interval": "Subscription bills every",
106
+ "dues.admin.plans.key": "Key — permanent",
107
+ "dues.admin.plans.length": "Pass length",
108
+ "dues.admin.plans.lifetime": "lifetime — one payment, forever",
109
+ "dues.admin.plans.missingPrice": "no price yet — not buyable",
110
+ "dues.admin.plans.month": "month",
111
+ "dues.admin.plans.months": "months",
112
+ "dues.admin.plans.name": "Name",
113
+ "dues.admin.plans.noSuch": "That plan could not be found.",
114
+ "dues.admin.plans.offSale": "off sale",
115
+ "dues.admin.plans.onSale": "Put it on sale",
116
+ "dues.admin.plans.price": "Price in minor units — 500 is £5.00",
117
+ "dues.admin.plans.putBack": "Put it back on sale",
118
+ "dues.admin.plans.restored": "{key} is back on sale.",
119
+ "dues.admin.plans.save": "Save the plan",
120
+ "dues.admin.plans.stripeError": "Stripe could not be reached. Nothing was saved — try again shortly.",
121
+ "dues.admin.plans.stripePrice": "Stripe price id — leave empty and one is minted to match",
122
+ "dues.admin.plans.stripeUnconfigured": "Stripe is not configured, so a subscription price cannot be minted. Set the secret key first, or paste a price id made in the Stripe dashboard.",
123
+ "dues.admin.plans.subscription": "a subscription — renews itself",
124
+ "dues.admin.plans.takeOffSale": "Take it off sale",
125
+ "dues.admin.plans.thePlans": "The plans",
126
+ "dues.admin.plans.tooLong": "A pass plus its grace window cannot reach past two years ({days} days) — that is the board’s cap on a plugin grant. Sell lifetime instead.",
127
+ "dues.admin.plans.unit": "…counted in",
128
+ "dues.admin.plans.updated": "{key} is updated. Existing memberships and running subscriptions keep what they bought; the change is for the next buyer.",
129
+ "dues.admin.plans.weeks": "weeks",
130
+ "dues.admin.plans.year": "year",
131
+ "dues.admin.plans.years": "years",
132
+ "dues.admin.status.attention": "{count, plural, one {# record needs attention} other {# records need attention}} — a payment that could not become a membership, or an amount that did not match its order. The members screen lists {count, plural, one {it} other {them}} first.",
133
+ "dues.admin.status.billing": "Billing",
134
+ "dues.admin.status.clear": "Clear the flag",
135
+ "dues.admin.status.days": "{count, plural, one {# day} other {# days}}",
136
+ "dues.admin.status.done": "done",
137
+ "dues.admin.status.events": "Latest webhook events",
138
+ "dues.admin.status.flagCleared": "Flag cleared.",
139
+ "dues.admin.status.flagMissing": "That flag could not be found — it may already be cleared.",
140
+ "dues.admin.status.giftable": "Giftable",
141
+ "dues.admin.status.grace": "Grace after a failed renewal",
142
+ "dues.admin.status.group": "Group",
143
+ "dues.admin.status.hidden": "hidden",
144
+ "dues.admin.status.keySet": "set",
145
+ "dues.admin.status.keyUnset": "not set — nothing can be bought",
146
+ "dues.admin.status.no": "no",
147
+ "dues.admin.status.none": "None yet. The first purchase, or Stripe’s “send test event” button, will put a row here — which is how you prove the endpoint works.",
148
+ "dues.admin.status.order": "Order {id} — {plan}, {amount}",
149
+ "dues.admin.status.orders": "Orders needing attention",
150
+ "dues.admin.status.ordersText": "Money moved but the order could not settle cleanly — most often an amount that did not match. Check the payment in Stripe’s dashboard, put it right there, then clear the flag here.",
151
+ "dues.admin.status.plan": "Plan",
152
+ "dues.admin.status.plans": "Plans on sale",
153
+ "dues.admin.status.plansText": "Plans are made and changed on the plans screen. Each grants membership of its group only while that group is marked “may be granted by plugins” under Admin → Groups; a purchase against a group that refuses shows up above as needing attention, with the payment kept and the reason recorded.",
154
+ "dues.admin.status.price": "Price",
155
+ "dues.admin.status.secret": "Stripe secret key",
156
+ "dues.admin.status.source": "Keys resolve environment-first: the settings form below this page shows which source is winning.",
157
+ "dues.admin.status.unprocessed": "unprocessed — the reconcile task retries it",
158
+ "dues.admin.status.webhook": "The webhook to create",
159
+ "dues.admin.status.webhookSecret": "Webhook signing secret",
160
+ "dues.admin.status.webhookText": "In the Stripe dashboard, add an endpoint at /api/plugins/dues/hook/stripe on this board’s public address, subscribed to exactly these events, and put its signing secret in the settings:",
161
+ "dues.admin.status.webhookUnset": "not set — payments cannot confirm",
162
+ "dues.admin.status.working": "Is it working?",
163
+ "dues.admin.status.yes": "yes",
164
+ "dues.definition.description": "Sells {label} — time-limited membership of a group — through Stripe, for yourself or as a gift.",
165
+ "dues.definition.go": "Over to Stripe",
166
+ "dues.definition.manage": "Your {label}",
167
+ "dues.definition.notification.gift.description": "A member bought a membership in your name. It starts the moment the payment confirms.",
168
+ "dues.definition.notification.gift.title": "Somebody gifts you a membership",
169
+ "dues.definition.notification.renewal.description": "A renewal did not go through. Access holds during the grace window while Stripe retries.",
170
+ "dues.definition.notification.renewal.title": "A membership payment fails",
171
+ "dues.definition.page.codes": "Discount codes",
172
+ "dues.definition.page.ledger": "Ledger",
173
+ "dues.definition.page.members": "Memberships",
174
+ "dues.definition.page.plans": "Plans",
175
+ "dues.definition.page.return": "Confirming your payment",
176
+ "dues.definition.page.status": "Status",
177
+ "dues.definition.setting.apiBase.description": "Only a stripe-mock or a test double ever changes this.",
178
+ "dues.definition.setting.apiBase.label": "Stripe API address",
179
+ "dues.definition.setting.apiVersion.description": "Pinned so Stripe’s payload shapes change on a deploy, not on a Tuesday. Change it only alongside a plugin upgrade that expects the new shapes.",
180
+ "dues.definition.setting.apiVersion.label": "Stripe API version",
181
+ "dues.definition.setting.secret.description": "The sk_live_… or sk_test_… key from the Stripe dashboard.",
182
+ "dues.definition.setting.secret.label": "Stripe secret key",
183
+ "dues.definition.setting.webhook.description": "The whsec_… secret of the webhook endpoint. Without it, payments are taken but can never confirm.",
184
+ "dues.definition.setting.webhook.label": "Webhook signing secret",
185
+ "dues.gifts.awaitingPayment": "awaiting payment",
186
+ "dues.gifts.delivered": "delivered",
187
+ "dues.gifts.heading": "Gifts you bought",
188
+ "dues.go.back": "Back to safety.",
189
+ "dues.go.continue": "continue by hand",
190
+ "dues.go.heading": "Over to Stripe…",
191
+ "dues.go.notAllowed": "That is not somewhere this board sends people.",
192
+ "dues.go.paymentLead": "Payment happens on Stripe’s own page — no card number ever touches this board. You should be there already;",
193
+ "dues.go.paymentTail": "if not.",
194
+ "dues.held.heading": "What you hold",
195
+ "dues.held.manage": "Manage your membership",
196
+ "dues.manage.cancel": "Cancel renewal — keep access until {date}",
197
+ "dues.manage.cancel-failed": "That could not be cancelled. If it keeps failing, contact a moderator.",
198
+ "dues.manage.cancelled": "Renewal cancelled. You keep everything you paid for until the period ends — nothing changes before then.",
199
+ "dues.manage.cardHeading": "Card and receipts",
200
+ "dues.manage.cardText": "Cards, invoices and receipts live on Stripe, where the payment did. The portal is theirs; it comes back here when you are done.",
201
+ "dues.manage.empty": "You hold no {label} yet.",
202
+ "dues.manage.grace": "A renewal payment failed. Stripe retries on its own; updating your card below usually settles it.",
203
+ "dues.manage.no-customer": "Stripe has no record for your account yet — the portal exists once you have bought something.",
204
+ "dues.manage.openPortal": "Open the billing portal",
205
+ "dues.manage.plans": "The plans are here.",
206
+ "dues.manage.sign-in": "Sign in first.",
207
+ "dues.manage.stripe-error": "Stripe could not be reached. Try again shortly.",
208
+ "dues.manage.title": "Your {label}",
209
+ "dues.manage.unconfigured": "Payments are not fully set up on this board.",
210
+ "dues.membership.activeAuto": "renews on",
211
+ "dues.membership.activeFixed": "yours until",
212
+ "dues.membership.closing": "will not renew — yours until",
213
+ "dues.membership.expired": "ended on",
214
+ "dues.membership.grace": "payment failed — access until",
215
+ "dues.membership.lifetime": "yours for good",
216
+ "dues.membership.revoked": "refunded and ended on",
217
+ "dues.notice.already-forever": "You hold this membership for good already — there is nothing left to buy for it.",
218
+ "dues.notice.already-member": "There is already an active subscription for that membership, so a second one would just charge twice for the same thing.",
219
+ "dues.notice.cancel-first": "There is an active subscription for that membership. Cancel its renewal first, then buy the lifetime plan — you keep everything already paid for.",
220
+ "dues.notice.cancelled": "Checkout was cancelled. Nothing was charged.",
221
+ "dues.notice.code-disabled": "That discount code has been switched off.",
222
+ "dues.notice.code-exhausted": "That discount code has been used as many times as it allows.",
223
+ "dues.notice.code-expired": "That discount code has expired.",
224
+ "dues.notice.code-wrong-plan": "That discount code is for a different plan.",
225
+ "dues.notice.gift-not-allowed": "That plan cannot be bought for someone else. Fixed-term passes can.",
226
+ "dues.notice.plan-not-ready": "That plan is not finished being set up — its Stripe price is missing. An administrator needs to complete it.",
227
+ "dues.notice.sign-in": "Sign in first, so the membership has an account to attach to.",
228
+ "dues.notice.stripe-error": "Stripe could not be reached, so nothing was charged. Try again shortly.",
229
+ "dues.notice.try-again": "That did not go through cleanly. Nothing was charged — try once more.",
230
+ "dues.notice.unconfigured": "Payments are not set up on this board yet. An administrator needs to finish the Stripe configuration before anything can be bought.",
231
+ "dues.notice.unknown-code": "That discount code does not exist. Check the spelling.",
232
+ "dues.notice.unknown-plan": "That plan is not on offer. The ones below are.",
233
+ "dues.notice.unknown-recipient": "No member goes by that name. Check the spelling — the gift needs somewhere to go.",
234
+ "dues.notification.gift.body": "It starts this moment — nothing to claim, nothing to do.",
235
+ "dues.notification.gift.subject": "Somebody bought you {plan}",
236
+ "dues.notification.renewal.body": "Stripe retries on its own. Your access holds during the grace window, and updating your card from the manage page usually settles it.",
237
+ "dues.notification.renewal.subject": "A membership payment did not go through",
238
+ "dues.plan.auto": "Renews automatically. Cancel any time and keep what you paid for until the period ends.",
239
+ "dues.plan.buyFixed": "Buy this pass",
240
+ "dues.plan.buyLifetime": "Buy lifetime membership",
241
+ "dues.plan.buySubscription": "Subscribe",
242
+ "dues.plan.discountCode": "Discount code, if you have one.",
243
+ "dues.plan.empty": "Nothing is on sale just now.",
244
+ "dues.plan.gift": "Buying for another member? Their username — leave empty for yourself.",
245
+ "dues.plan.lifetime": "One payment, no renewal, no end date.",
246
+ "dues.plan.payment": "Payment is taken by Stripe on their own checkout page — no card number ever touches this board.",
247
+ "dues.plan.signIn": "Sign in to join",
248
+ "dues.return.backToPlans": "Back to the plans",
249
+ "dues.return.backToShop": "Back to {label}",
250
+ "dues.return.checkAgain": "Check again",
251
+ "dues.return.checkout": "This checkout {status}.",
252
+ "dues.return.confirming": "Confirming your payment…",
253
+ "dues.return.confirmingText": "Stripe is telling the board about your payment right now. This page does not grant anything by itself — the confirmation does, and it usually lands within seconds.",
254
+ "dues.return.failed": "did not go through",
255
+ "dues.return.giftPaid": "Your gift of {plan} has been delivered. They hold it from this moment.",
256
+ "dues.return.noOrder": "There is no order of yours here.",
257
+ "dues.return.nothingCharged": "Nothing was charged",
258
+ "dues.return.paid": "Paid, and done",
259
+ "dues.return.paidPlan": "{plan} is yours from this moment.",
260
+ "dues.return.payment": "{amount} — Stripe sends the receipt.",
261
+ "dues.return.seeMembership": "See your membership",
262
+ "dues.return.wasCancelled": "was cancelled"
263
+ }
@@ -0,0 +1,5 @@
1
+ import type { CatalogBundle } from '@meith/theme-kit'
2
+
3
+ import en from './en.json'
4
+
5
+ export const duesMessages: CatalogBundle = { en }
package/src/money.ts CHANGED
@@ -1,7 +1,20 @@
1
-
2
1
  const ZERO_DECIMAL = new Set([
3
- 'bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga',
4
- 'pyg', 'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf',
2
+ 'bif',
3
+ 'clp',
4
+ 'djf',
5
+ 'gnf',
6
+ 'jpy',
7
+ 'kmf',
8
+ 'krw',
9
+ 'mga',
10
+ 'pyg',
11
+ 'rwf',
12
+ 'ugx',
13
+ 'vnd',
14
+ 'vuv',
15
+ 'xaf',
16
+ 'xof',
17
+ 'xpf',
5
18
  ])
6
19
 
7
20
  export function isZeroDecimal(currency: string): boolean {
@@ -16,12 +29,12 @@ export function isValidMinorAmount(value: unknown): value is number {
16
29
  return typeof value === 'number' && Number.isSafeInteger(value) && value > 0
17
30
  }
18
31
 
19
- export function formatMinor(amountMinor: number, currency: string): string {
32
+ export function formatMinor(amountMinor: number, currency: string, locale = 'en'): string {
20
33
  const upper = currency.toUpperCase()
21
34
  const amount = isZeroDecimal(currency) ? amountMinor : amountMinor / 100
22
35
 
23
36
  try {
24
- return new Intl.NumberFormat('en', {
37
+ return new Intl.NumberFormat(locale, {
25
38
  style: 'currency',
26
39
  currency: upper,
27
40
  minimumFractionDigits: isZeroDecimal(currency) ? 0 : 2,
package/src/period.ts CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  export interface Period {
3
2
  readonly years: number
4
3
  readonly months: number
@@ -38,9 +37,7 @@ export function addPeriod(from: Date, period: Period): Date {
38
37
  const dayOfMonth = out.getUTCDate()
39
38
  out.setUTCDate(1)
40
39
  out.setUTCMonth(out.getUTCMonth() + months)
41
- const lastDay = new Date(
42
- Date.UTC(out.getUTCFullYear(), out.getUTCMonth() + 1, 0),
43
- ).getUTCDate()
40
+ const lastDay = new Date(Date.UTC(out.getUTCFullYear(), out.getUTCMonth() + 1, 0)).getUTCDate()
44
41
  out.setUTCDate(Math.min(dayOfMonth, lastDay))
45
42
  }
46
43
 
package/src/plans.ts CHANGED
@@ -2,7 +2,7 @@ import type { PluginData } from '@meith/plugin-kit'
2
2
 
3
3
  import type { DuesConfig } from './config'
4
4
  import { isCurrencyCode, isValidMinorAmount } from './money'
5
- import { addDays, parsePeriod, periodCeilingDays, type Period } from './period'
5
+ import { addDays, type Period, parsePeriod, periodCeilingDays } from './period'
6
6
  import {
7
7
  countPlans,
8
8
  insertPlan,
@@ -29,10 +29,7 @@ export function isLifetime(periodEnd: Date): boolean {
29
29
  return periodEnd >= LIFETIME_END
30
30
  }
31
31
 
32
- export async function loadPlans(
33
- data: PluginData,
34
- config: DuesConfig,
35
- ): Promise<readonly PlanRow[]> {
32
+ export async function loadPlans(data: PluginData, config: DuesConfig): Promise<readonly PlanRow[]> {
36
33
  if ((await countPlans(data)) === 0 && config.seedPlans.length > 0) {
37
34
  for (const seed of config.seedPlans) {
38
35
  await insertPlan(data, {
@@ -55,10 +52,7 @@ export async function loadPlans(
55
52
  return listPlans(data)
56
53
  }
57
54
 
58
- export async function shopPlans(
59
- data: PluginData,
60
- config: DuesConfig,
61
- ): Promise<readonly PlanRow[]> {
55
+ export async function shopPlans(data: PluginData, config: DuesConfig): Promise<readonly PlanRow[]> {
62
56
  return (await loadPlans(data, config)).filter((plan) => !plan.hidden && !plan.archived)
63
57
  }
64
58
 
@@ -149,7 +143,15 @@ export function parsePlanForm(form: PlanFormInput, graceDays: number): PlanParse
149
143
  const length = Number(form.length ?? '')
150
144
  const unit = form.unit ?? ''
151
145
  const letter =
152
- unit === 'days' ? 'D' : unit === 'weeks' ? 'W' : unit === 'months' ? 'M' : unit === 'years' ? 'Y' : null
146
+ unit === 'days'
147
+ ? 'D'
148
+ : unit === 'weeks'
149
+ ? 'W'
150
+ : unit === 'months'
151
+ ? 'M'
152
+ : unit === 'years'
153
+ ? 'Y'
154
+ : null
153
155
  if (letter === null || !Number.isInteger(length) || length < 1) return bad('bad-length')
154
156
 
155
157
  periodSpec = `P${length}${letter}`
package/src/store.ts CHANGED
@@ -114,7 +114,10 @@ function membershipRow(row: Record<string, unknown>): MembershipRow {
114
114
  graceUntil: asDate(row.grace_until),
115
115
  needsAttention: asNullableString(row.needs_attention),
116
116
  stripeSubscriptionId: asNullableString(row.stripe_subscription_id),
117
- lastOrderId: row.last_order_id === null || row.last_order_id === undefined ? null : Number(row.last_order_id),
117
+ lastOrderId:
118
+ row.last_order_id === null || row.last_order_id === undefined
119
+ ? null
120
+ : Number(row.last_order_id),
118
121
  }
119
122
  }
120
123
 
@@ -137,7 +140,9 @@ function ledgerRow(row: Record<string, unknown>): LedgerRow {
137
140
  kind: String(row.kind) as LedgerRow['kind'],
138
141
  userId: Number(row.user_id),
139
142
  membershipId:
140
- row.membership_id === null || row.membership_id === undefined ? null : Number(row.membership_id),
143
+ row.membership_id === null || row.membership_id === undefined
144
+ ? null
145
+ : Number(row.membership_id),
141
146
  orderId: row.order_id === null || row.order_id === undefined ? null : Number(row.order_id),
142
147
  amountMinor: Number(row.amount_minor),
143
148
  currency: String(row.currency),
@@ -329,10 +334,7 @@ export async function liveMembership(
329
334
  return row === null ? null : membershipRow(row)
330
335
  }
331
336
 
332
- export async function membershipById(
333
- data: PluginData,
334
- id: number,
335
- ): Promise<MembershipRow | null> {
337
+ export async function membershipById(data: PluginData, id: number): Promise<MembershipRow | null> {
336
338
  const row = await data.one('select * from plugin_dues_membership where id = $1', [id])
337
339
  return row === null ? null : membershipRow(row)
338
340
  }
@@ -448,11 +450,7 @@ export async function setMembershipStatus(
448
450
  )
449
451
  }
450
452
 
451
- export async function flagMembership(
452
- data: PluginData,
453
- id: number,
454
- reason: string,
455
- ): Promise<void> {
453
+ export async function flagMembership(data: PluginData, id: number, reason: string): Promise<void> {
456
454
  await data.query(
457
455
  `update plugin_dues_membership set needs_attention = $2, updated_at = now() where id = $1`,
458
456
  [id, reason],
@@ -519,10 +517,9 @@ export async function recordEvent(
519
517
  )
520
518
  if (inserted !== null) return { id: Number(inserted.id), first: true }
521
519
 
522
- const existing = await data.one(
523
- 'select id from plugin_dues_event where stripe_event_id = $1',
524
- [event.stripeEventId],
525
- )
520
+ const existing = await data.one('select id from plugin_dues_event where stripe_event_id = $1', [
521
+ event.stripeEventId,
522
+ ])
526
523
  if (existing === null) throw new Error('event insert lost a race it cannot lose')
527
524
  return { id: Number(existing.id), first: false }
528
525
  }
@@ -613,7 +610,10 @@ export interface MonthlyTotal {
613
610
  readonly charges: number
614
611
  }
615
612
 
616
- export async function monthlyTotals(data: PluginData, months = 12): Promise<readonly MonthlyTotal[]> {
613
+ export async function monthlyTotals(
614
+ data: PluginData,
615
+ months = 12,
616
+ ): Promise<readonly MonthlyTotal[]> {
617
617
  const rows = await data.query(
618
618
  `select to_char(date_trunc('month', occurred_at), 'YYYY-MM') as month,
619
619
  currency,
@@ -712,10 +712,7 @@ export interface NewCode {
712
712
  readonly createdByUserId: number
713
713
  }
714
714
 
715
- export async function insertCode(
716
- data: PluginData,
717
- code: NewCode,
718
- ): Promise<CodeRow | null> {
715
+ export async function insertCode(data: PluginData, code: NewCode): Promise<CodeRow | null> {
719
716
  const row = await data.one(
720
717
  `insert into plugin_dues_code
721
718
  (code, percent_off, plan_key, max_redemptions, expires_at, created_by_user_id)
@@ -735,10 +732,7 @@ export async function insertCode(
735
732
  }
736
733
 
737
734
  export async function codeByCode(data: PluginData, code: string): Promise<CodeRow | null> {
738
- const row = await data.one(
739
- 'select * from plugin_dues_code where lower(code) = lower($1)',
740
- [code],
741
- )
735
+ const row = await data.one('select * from plugin_dues_code where lower(code) = lower($1)', [code])
742
736
  return row === null ? null : codeRow(row)
743
737
  }
744
738
 
@@ -922,16 +916,14 @@ export async function setPlanArchived(
922
916
  id: number,
923
917
  archived: boolean,
924
918
  ): Promise<void> {
925
- await data.query(
926
- `update plugin_dues_plan set archived = $2, updated_at = now() where id = $1`,
927
- [id, archived],
928
- )
919
+ await data.query(`update plugin_dues_plan set archived = $2, updated_at = now() where id = $1`, [
920
+ id,
921
+ archived,
922
+ ])
929
923
  }
930
924
 
931
925
  export async function listPlans(data: PluginData): Promise<readonly PlanRow[]> {
932
- const rows = await data.query(
933
- `select * from plugin_dues_plan order by archived, created_at, id`,
934
- )
926
+ const rows = await data.query(`select * from plugin_dues_plan order by archived, created_at, id`)
935
927
  return rows.map(planRow)
936
928
  }
937
929
 
@@ -1,4 +1,3 @@
1
-
2
1
  export interface StripeClientOptions {
3
2
  readonly secretKey: string
4
3
  readonly apiVersion: string
@@ -31,7 +30,9 @@ export function encodeStripeForm(body: Record<string, unknown>): string {
31
30
  const walk = (prefix: string, value: unknown): void => {
32
31
  if (value === undefined || value === null) return
33
32
  if (Array.isArray(value)) {
34
- value.forEach((entry, index) => walk(`${prefix}[${index}]`, entry))
33
+ value.forEach((entry, index) => {
34
+ walk(`${prefix}[${index}]`, entry)
35
+ })
35
36
  return
36
37
  }
37
38
  if (typeof value === 'object') {
@@ -68,7 +69,11 @@ export interface SubscriptionState {
68
69
 
69
70
  function str(value: unknown): string | null {
70
71
  if (typeof value === 'string') return value
71
- if (typeof value === 'object' && value !== null && typeof (value as { id?: unknown }).id === 'string') {
72
+ if (
73
+ typeof value === 'object' &&
74
+ value !== null &&
75
+ typeof (value as { id?: unknown }).id === 'string'
76
+ ) {
72
77
  return (value as { id: string }).id
73
78
  }
74
79
  return null
@@ -89,7 +94,8 @@ export function sessionFromApi(body: Record<string, unknown>): CheckoutSessionSt
89
94
  }
90
95
 
91
96
  export function subscriptionFromApi(body: Record<string, unknown>): SubscriptionState {
92
- let periodEnd: number | null = typeof body.current_period_end === 'number' ? body.current_period_end : null
97
+ let periodEnd: number | null =
98
+ typeof body.current_period_end === 'number' ? body.current_period_end : null
93
99
 
94
100
  if (periodEnd === null) {
95
101
  const items = (body.items as { data?: unknown } | undefined)?.data
@@ -179,9 +185,7 @@ export function createStripeClient(options: StripeClientOptions): StripeClient {
179
185
  headers: {
180
186
  authorization: `Bearer ${options.secretKey}`,
181
187
  'stripe-version': options.apiVersion,
182
- ...(body === undefined
183
- ? {}
184
- : { 'content-type': 'application/x-www-form-urlencoded' }),
188
+ ...(body === undefined ? {} : { 'content-type': 'application/x-www-form-urlencoded' }),
185
189
  ...(idempotencyKey === undefined ? {} : { 'idempotency-key': idempotencyKey }),
186
190
  },
187
191
  ...(body === undefined ? {} : { body: encodeStripeForm(body) }),
@@ -1,4 +1,3 @@
1
-
2
1
  export interface StripeEventEnvelope {
3
2
  readonly id: string
4
3
  readonly type: string
@@ -68,9 +67,7 @@ function invoiceSubscriptionId(invoice: Record<string, unknown>): string | null
68
67
  const direct = idOf(invoice.subscription)
69
68
  if (direct !== null) return direct
70
69
 
71
- const parent = invoice.parent as
72
- | { subscription_details?: { subscription?: unknown } }
73
- | undefined
70
+ const parent = invoice.parent as { subscription_details?: { subscription?: unknown } } | undefined
74
71
  return idOf(parent?.subscription_details?.subscription)
75
72
  }
76
73
 
@@ -79,7 +76,7 @@ function invoicePeriodEnd(invoice: Record<string, unknown>): Date | null {
79
76
  let latest: number | null = null
80
77
  if (Array.isArray(lines)) {
81
78
  for (const line of lines) {
82
- const end = ((line as { period?: { end?: unknown } }).period ?? {}).end
79
+ const end = (line as { period?: { end?: unknown } }).period?.end
83
80
  if (typeof end === 'number' && (latest === null || end > latest)) latest = end
84
81
  }
85
82
  }
package/src/tasks.ts CHANGED
@@ -1,12 +1,12 @@
1
+ import { applyInternalEvent, type EntitlementDeps, settlePaidOrder } from './entitlement'
1
2
  import { addDays } from './period'
2
3
  import { clampGrantUntil, TOP_UP_WHEN_WITHIN_DAYS } from './plans'
3
- import { applyInternalEvent, settlePaidOrder, type EntitlementDeps } from './entitlement'
4
4
  import {
5
5
  expireDueMemberships,
6
6
  extendMembership,
7
7
  longLiveMemberships,
8
- markEventProcessed,
9
8
  markEventFailed,
9
+ markEventProcessed,
10
10
  membershipsPastPeriod,
11
11
  pendingOrdersOlderThan,
12
12
  setMembershipStatus,