@mesub/node 0.1.0 → 0.1.3
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 +10 -5
- package/dist/{chunk-ELK7IBLO.js → chunk-35MOUS4W.js} +2 -2
- package/dist/{chunk-XDLLXY7Y.js → chunk-6HGYY2KN.js} +2 -2
- package/dist/{chunk-SGSYQJYT.cjs → chunk-BP5D5I66.cjs} +31 -22
- package/dist/chunk-BP5D5I66.cjs.map +1 -0
- package/dist/{chunk-KMPBFNJE.js → chunk-JCDJZDEJ.js} +16 -7
- package/dist/chunk-JCDJZDEJ.js.map +1 -0
- package/dist/{chunk-PCDZI7B4.cjs → chunk-RSN2QQUR.cjs} +7 -7
- package/dist/{chunk-PCDZI7B4.cjs.map → chunk-RSN2QQUR.cjs.map} +1 -1
- package/dist/{chunk-VMKI4VVA.cjs → chunk-WNHLTJ6F.cjs} +4 -4
- package/dist/{chunk-VMKI4VVA.cjs.map → chunk-WNHLTJ6F.cjs.map} +1 -1
- package/dist/{client-Dm1jamju.d.cts → client-BZB6KIGd.d.cts} +10 -3
- package/dist/{client-BQuRYSdB.d.ts → client-Q5QhXphp.d.ts} +10 -3
- package/dist/{errors-DqW3DMBS.d.ts → errors-BN4ZWUfM.d.ts} +1 -1
- package/dist/{errors-CtPucyOh.d.cts → errors-DcLfunS-.d.cts} +1 -1
- package/dist/express.cjs +21 -21
- package/dist/express.d.cts +7 -7
- package/dist/express.d.ts +7 -7
- package/dist/express.js +3 -3
- package/dist/{guard-DKb6iU1z.d.ts → guard-CaXiHMVF.d.ts} +1 -1
- package/dist/{guard-Dhy4fI6n.d.cts → guard-D4AB5HtN.d.cts} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/nest.cjs +13 -13
- package/dist/nest.d.cts +5 -5
- package/dist/nest.d.ts +5 -5
- package/dist/nest.js +2 -2
- package/dist/next.cjs +20 -20
- package/dist/next.d.cts +7 -7
- package/dist/next.d.ts +7 -7
- package/dist/next.js +3 -3
- package/dist/{routes-DWkRFOP0.d.cts → routes-DT8EFWSl.d.cts} +2 -2
- package/dist/{routes-CLtZnUzl.d.ts → routes-KoaD1Xsk.d.ts} +2 -2
- package/dist/situations.d.cts +1 -1
- package/dist/situations.d.ts +1 -1
- package/dist/{subscriptions-D1hE7MUc.d.ts → subscriptions-B6Lin6i_.d.cts} +7 -0
- package/dist/{subscriptions-D1hE7MUc.d.cts → subscriptions-B6Lin6i_.d.ts} +7 -0
- package/dist/testing.cjs +20 -18
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +8 -6
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-KMPBFNJE.js.map +0 -1
- package/dist/chunk-SGSYQJYT.cjs.map +0 -1
- /package/dist/{chunk-ELK7IBLO.js.map → chunk-35MOUS4W.js.map} +0 -0
- /package/dist/{chunk-XDLLXY7Y.js.map → chunk-6HGYY2KN.js.map} +0 -0
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["/**\n * `@mesub/node/testing`: a fake Mesub, for a merchant's own tests. It answers\n * what the SDK calls (`/v1/access`, `/v1/plans`, `/v1/subscriptions`, its\n * fresh transaction, its attempts and its cancel, resume and close, and\n * `/v1/events`) from what the test sets, through a `fetch` handed to the\n * client: no network, no backend, no test framework of its own.\n *\n * ```ts\n * import { FakeMesub } from '@mesub/node/testing';\n *\n * const fake = new FakeMesub();\n * fake.grant(wallet, 'pro');\n * const mesub = fake.client();\n * await mesub.hasAccess(wallet, 'pro'); // true\n *\n * const { body, headers } = await fake.webhook('subscription.renewed');\n * await mesub.webhooks.verify(body, headers); // the event\n * ```\n */\nimport type { AccessAnswer, Customer, ServedAttempt } from './answer.js';\nimport { Mesub, type MesubOptions } from './client.js';\nimport { type Asked, customerOf } from './customer.js';\nimport type { EventList } from './events.js';\nimport type { Plan, PlanToken } from './plans.js';\nimport type {\n ServerSubscription,\n ServerSubscriptionList,\n SubscribeTransaction,\n SubscriptionAttempt,\n SubscriptionAttemptList,\n WalletTransaction,\n} from './subscriptions.js';\nimport { API_VERSION, API_VERSION_HEADER } from './version.js';\nimport {\n type EventType,\n type MesubEvent,\n signedHeaders,\n type WebhookEventType,\n} from './webhooks.js';\n\nexport interface FakeMesubOptions {\n /** Where the fake answers. Defaults to `https://api.mesub.test`. */\n baseUrl?: string;\n /** The only key it accepts; any other is answered 401. Defaults to `SUB_test_fake`. */\n apiKey?: string;\n /**\n * The plans that exist: any other slug is answered 404 `plan_not_found`.\n * By default every slug exists. A slug alone, or the fields `plans.list`\n * and `plans.retrieve` should answer for it: the rest is filled in.\n * Name them to test the widget routes, which read `plans.list`: left\n * out, the list is empty. A plan given an `ends_at` is served as Mesub\n * serves one: no `access_until` later than it, no pull dated from it\n * on, and no access once it has passed. A plan's token is USDC, verified,\n * and its `amount_usd` its `amount_display`: give a `token` (some of its\n * fields are enough) and an `amount_usd` for any other, such as an\n * unverified one with a null `symbol`.\n */\n plans?: Array<string | FakePlanFields>;\n /** What `webhook()` signs with, and `client()` verifies with. Defaults to a fixed `whsec_` secret. */\n webhookSecret?: string;\n /**\n * False to act as a Mesub that predates\n * `GET /v1/subscriptions/:id/attempts`: it answers Nest's 404 for a route\n * it does not have, and attempts are only served through `/v1/access`.\n */\n attemptsRoute?: boolean;\n /**\n * Mesub's `available_since`: events created before it are not served on\n * `/v1/events`. Defaults to 30 days before the fake was made, so every\n * event of the window is.\n */\n eventsSince?: string | Date;\n /**\n * The `revalidate_after` of every `/v1/access` answer, in seconds: 0 for\n * a test that wants each change seen on the next call of the same\n * client. Left out, Mesub's own: 10 for a no, and for a yes the time to\n * its next charge, retry or end, from 10 to 300, so a client keeps a yes\n * up to five minutes. One set on an answer with `setAccess` wins.\n */\n revalidateAfter?: number;\n /**\n * True to raise `subscription.access_granted` and\n * `subscription.access_revoked` (Mesub-io/backend#507) when a change the\n * fake makes flips access, right after the event that says why, one\n * `sequence` later: a submit grants. False by default, as on Mesub until\n * it turns them on. The fake moves no clock, so a date\n * passing revokes nothing: record that with `addEvent`, which, like\n * `webhook`, makes either type whatever this says.\n */\n accessEvents?: boolean;\n /**\n * The project's tier, `dev` by default. It decides what Pay now\n * (`subscriptions.retry`) refuses, as on Mesub: on `free` a wait of 10\n * minutes after each hand retry, three per missed period, none past\n * `retry_deadline`; on `dev` and `business` none for the first, then a\n * wait of 20 minutes doubling after each. And what `addSubscription`\n * gives a late row: no access on `free`, access through its retries\n * otherwise.\n */\n tier?: FakeTier;\n}\n\n/** A project's tier, as Mesub names them. */\nexport type FakeTier = 'free' | 'dev' | 'business';\n\n/** A plan as declared to the fake: its slug, and any field to set, a token's included. */\nexport type FakePlanFields = Partial<Omit<Plan, 'token'>> & {\n slug: string;\n token?: Partial<PlanToken>;\n};\n\n/** How many attempts `/v1/access` serves at most. */\nconst ACCESS_ATTEMPTS = 5;\n\n/** A webhook as Mesub posts it: the exact body, and the three headers that sign it. */\nexport interface SignedWebhook {\n body: string;\n headers: Record<string, string>;\n}\n\nexport interface SignWebhookOptions {\n /** The endpoint's secret, `whsec_...`. */\n secret: string;\n /**\n * `webhook-id`. Defaults to a new `msg_signed_...` each time, never one\n * a fake's `webhook()` gives.\n */\n id?: string;\n /** `webhook-timestamp`, Unix seconds. Defaults to now. */\n timestamp?: number;\n}\n\n/** What `FakeMesub.addEvent` records, on top of a made-up event of that type. */\nexport interface FakeEventFields {\n /**\n * Fields of the subscription it carries, on top of the one the fake\n * holds under that `id`, else of an active, paid one on `pro`.\n */\n subscription?: Partial<ServerSubscription>;\n /** The event's detail. Defaults to a plausible one for the type. */\n detail?: Record<string, unknown>;\n /** When the event happened. Defaults to now. */\n created_at?: string;\n /** The event's id. Defaults to a new `evt_fake_...`. */\n id?: string;\n /**\n * Its place among its subscription's events. Defaults to the next one\n * for that subscription; null leaves it out, as on an event Mesub could\n * not number.\n */\n sequence?: number | null;\n}\n\n/** What `FakeMesub.webhook` sends, on top of a made-up event of that type. */\nexport interface FakeWebhookFields {\n /**\n * Fields of the subscription it carries, on top of the one the fake\n * holds under that `id`, else of an active, paid one on `pro`. The\n * `test` event carries Mesub's sample one (`sub_test`, `user_123`).\n */\n subscription?: Partial<ServerSubscription>;\n /** The event's detail. Defaults to a plausible one for the type. */\n detail?: Record<string, unknown>;\n /** When the event happened. Defaults to now. */\n created_at?: string;\n /**\n * The event's id. Defaults to a new `evt_fake_...`, and to `evt_test` for\n * the `test` event, as Mesub's test deliveries carry.\n */\n id?: string;\n /**\n * Its place among its subscription's events. Defaults to the next one\n * for that subscription, and to 1 on the `test` event, as Mesub's test\n * deliveries carry; null leaves it out, as on an event Mesub could not\n * number.\n */\n sequence?: number | null;\n /**\n * The delivery's id, the `webhook-id` header. Defaults to the fake's\n * next `msg_fake_...`, from `msg_fake_1`, and again after `reset()`.\n */\n delivery_id?: string;\n /** `webhook-timestamp`, Unix seconds. Defaults to now. */\n timestamp?: number;\n}\n\n/** One call the fake received. */\nexport interface FakeRequest {\n method: string;\n /** Under the base URL, e.g. `/v1/access`. */\n path: string;\n query: Record<string, string>;\n headers: Headers;\n /** The parsed JSON body of a POST, undefined otherwise. */\n body: unknown;\n}\n\n/** An error the fake answers instead, as Mesub would send it. */\nexport interface FakeFailure {\n status: number;\n /** Mesub's own code, e.g. `network_unavailable`. */\n code: string;\n message?: string;\n /**\n * Defaults to what Mesub says of the code, e.g. true for\n * `subscription_changed`; for a code Mesub does not have, to what the\n * status says: true for 408, 429 and 5xx.\n */\n retryable?: boolean;\n /**\n * Seconds, sent as `Retry-After`. A 503 on a route that builds, sends or\n * queues (every POST under `/v1/subscriptions`) defaults to Mesub's own:\n * 2 for `unavailable` on submit (too many submits waiting), 20 for\n * `unavailable` on retry (the charge could not be queued), 10 otherwise.\n * `retry_too_soon` defaults to the first wait of the fake's `tier`: 600\n * on `free`, 1200 on `dev` and `business`.\n */\n retryAfter?: number;\n}\n\n/** What a test may set of an access answer: all but the plan, named apart. */\nexport type FakeAccess = Partial<Omit<AccessAnswer, 'plan'>>;\n\n/**\n * An access answer as the fake holds it: `revalidate_after`, `closable` and\n * `closable_from` only when a test set them, worked out when served otherwise.\n */\ntype Held = Omit<AccessAnswer, 'revalidate_after' | 'closable' | 'closable_from'> &\n Partial<Pick<AccessAnswer, 'revalidate_after' | 'closable' | 'closable_from'>>;\n\n/** What Mesub answers a customer with nothing on a plan. */\nconst NOTHING: Omit<Held, 'wallet' | 'plan'> = {\n subscription_id: null,\n access: false,\n status: 'none',\n paused: false,\n end_reason: null,\n late_reason: null,\n stop_reason: null,\n payment_status: 'none',\n blocked_since: null,\n blocked_reason: null,\n subscribed_since: null,\n first_subscribed_at: null,\n current_period_end: null,\n cancelled_at: null,\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n};\n\n/** The freshest and the stalest Mesub serves an answer, in seconds (the back's `AccessService`). */\nconst REVALIDATE_MIN_S = 10;\nconst REVALIDATE_MAX_S = 300;\n\nconst OUTAGE: FakeFailure = {\n status: 503,\n code: 'unavailable',\n message: 'Mesub is unavailable.',\n};\n\n/** 24 bytes, as Mesub's secrets are. Only ever a test's. */\nconst FAKE_WEBHOOK_SECRET = 'whsec_bWVzdWItZmFrZS13ZWJob29rLWtleS0x';\nconst FAKE_WALLET = '11111111111111111111111111111111';\n/** The id every test delivery carries, as Mesub's: no event stands behind it. */\nconst TEST_EVENT_ID = 'evt_test';\n/** How far back `/v1/events` reaches, as on Mesub. */\nconst EVENTS_WINDOW_MS = 30 * 24 * 3600 * 1000;\n/** What `/v1/events` serves per page, by default and at most. */\nconst EVENTS_LIMIT = 25;\nconst EVENTS_LIMIT_MAX = 100;\n/** The v1 event types, as Mesub names them and `type` is checked against. */\nconst EVENT_TYPES: readonly EventType[] = [\n 'subscription.created',\n 'subscription.renewal_upcoming',\n 'subscription.renewed',\n 'subscription.payment_failed',\n 'subscription.stopped',\n 'subscription.cancelled',\n 'subscription.resumed',\n 'subscription.ended',\n 'subscription.expired',\n 'subscription.access_granted',\n 'subscription.access_revoked',\n 'subscription.paused',\n 'subscription.unpaused',\n];\n/** An id as Mesub's DTOs take one. */\nconst RESOURCE_ID = /^[A-Za-z0-9_-]{1,64}$/;\n/** The header Mesub names every request in (Mesub-io/backend#472). */\nconst REQUEST_ID_HEADER = 'Mesub-Request-Id';\nconst USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\nconst PERIOD_MS = 30 * 24 * 3600 * 1000;\nconst DAY_MS = 24 * 3600 * 1000;\n/** The id and the made-up subscription of every test delivery, as Mesub's (`TEST_SUBSCRIPTION`). */\nconst TEST_SUBSCRIPTION: ServerSubscription = {\n id: 'sub_test',\n status: 'active',\n paused: false,\n end_reason: null,\n late_reason: null,\n stop_reason: null,\n access: true,\n payment_status: 'paid',\n blocked_since: null,\n blocked_reason: null,\n plan: 'pro',\n wallet: FAKE_WALLET,\n email: 'subscriber@example.com',\n external_id: 'user_123',\n current_period_start: '2026-01-01T00:00:00.000Z',\n current_period_end: '2026-01-31T00:00:00.000Z',\n next_charge_at: '2026-01-31T00:00:00.000Z',\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n access_until: '2026-01-31T00:00:00.000Z',\n closable: false,\n closable_from: null,\n created_at: '2026-01-01T00:00:00.000Z',\n confirmed_at: '2026-01-01T00:00:00.000Z',\n};\n/** Free: the wait after each hand retry (`MANUAL_RETRY_COOLDOWN_MS`), and how many a missed period gets. */\nconst FREE_RETRY_WAIT_MS = 10 * 60 * 1000;\nconst FREE_HAND_RETRIES = 3;\n/** Dev and Business: the wait after the first hand retry, doubling after each (`PAID_HAND_RETRY_WAIT_MS`). */\nconst PAID_RETRY_WAIT_MS = 20 * 60 * 1000;\n/** Two minutes before the end of the missed period, where Free's hand retries close (`retryClosesAt`). */\nconst CLOCK_MARGIN_MS = 2 * 60 * 1000;\n/** About 150 blocks of 200 ms: how long Mesub says a transaction it built lives. */\nconst TRANSACTION_LIFE_MS = 30_000;\nconst REASONS: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n 503: 'Service Unavailable',\n};\n\n/**\n * A fake Mesub API. Each test sets what a customer has with `grant`, `deny`\n * or `setAccess`, gets a client wired to it with `client()`, and makes it\n * fail with `fail()`. A plan with an end is `grantLastPeriod`, `endPlan`, or\n * an `ends_at` in `plans`.\n *\n * It answers and refuses as Mesub does, `revalidate_after` included: a\n * client keeps a yes up to five minutes and a no 10 seconds, so a change\n * made after a client asked shows to a new client, or to every client with\n * `revalidateAfter: 0`. Every answer carries a `Mesub-Request-Id`, and every\n * error body its `request_id`.\n *\n * `closable` and `closable_from` are worked out from what the fake's close\n * would take, unless a test set them on an answer. A subscription it moves\n * raises the event Mesub raises (`subscription.created` on a submit,\n * `cancelled`, `resumed` and `ended` on a confirm), which `/v1/events`\n * serves; `addEvent` records any other.\n */\nexport class FakeMesub {\n readonly baseUrl: string;\n readonly apiKey: string;\n readonly webhookSecret: string;\n /** Every call received, oldest first. */\n readonly requests: FakeRequest[] = [];\n /** Hand it to `new Mesub({ fetch })`, or use `client()`. */\n readonly fetch: typeof globalThis.fetch;\n\n readonly #plans: Set<string> | null;\n readonly #planList: Plan[];\n readonly #answers = new Map<string, Held>();\n /** The ends `grantLastPeriod` and `endPlan` gave a plan, by slug: they win over `plans`. */\n readonly #ends = new Map<string, string>();\n #subscriptions: ServerSubscription[] = [];\n /** The subscriptions a cancel, resume or close transaction was built for, by id. */\n readonly #built = new Map<string, Set<FakeAction>>();\n /** The rows ended with their plan whose close landed: nothing of them is left on chain. */\n readonly #gone = new Set<string>();\n /** The attempts `setAttempts` gave each subscription, newest first, by id. */\n readonly #attempts = new Map<string, SubscriptionAttempt[]>();\n readonly #attemptsRoute: boolean;\n readonly #revalidateAfter: number | undefined;\n #attemptIds = 0;\n #eventIds = 0;\n /** The last `sequence` given to each subscription's events, by id. */\n readonly #sequences = new Map<string, number>();\n readonly #accessEvents: boolean;\n #requestIds = 0;\n /** Every event raised or added, as `/v1/events` serves it. */\n #events: MesubEvent[] = [];\n readonly #eventsSince: string;\n #failure: FakeFailure | null = null;\n #ids = 0;\n /** The made-up subscriptions of `webhook` and `addEvent`, apart: they take no `sub_fake_` id. */\n #madeUpIds = 0;\n #deliveryIds = 0;\n readonly #tier: FakeTier;\n /** The hand retries fired on each late subscription, and when the last one was, by id. */\n readonly #handRetries = new Map<string, { count: number; at: number }>();\n\n constructor(options: FakeMesubOptions = {}) {\n this.baseUrl = (options.baseUrl ?? 'https://api.mesub.test').replace(/\\/+$/, '');\n this.apiKey = options.apiKey ?? 'SUB_test_fake';\n const plans = options.plans?.map((plan) =>\n typeof plan === 'string' ? fakePlan(plan) : fakePlan(plan.slug, plan),\n );\n this.#plans = plans ? new Set(plans.map((plan) => plan.slug)) : null;\n this.#planList = (plans ?? []).sort((a, b) => a.slug.localeCompare(b.slug));\n this.webhookSecret = options.webhookSecret ?? FAKE_WEBHOOK_SECRET;\n this.#attemptsRoute = options.attemptsRoute ?? true;\n this.#revalidateAfter = options.revalidateAfter;\n this.#accessEvents = options.accessEvents ?? false;\n this.#tier = options.tier ?? 'dev';\n this.#eventsSince = new Date(\n options.eventsSince ?? Date.now() - EVENTS_WINDOW_MS,\n ).toISOString();\n this.fetch = (input, init) => this.#handle(input, init);\n }\n\n /**\n * A client wired to this fake: its key, base URL and fetch, and no retry\n * unless asked, so a failure shows at once. Any other option is passed on.\n */\n client(\n options: Omit<MesubOptions, 'apiKey' | 'baseUrl' | 'fetch' | 'webhookSecret'> = {},\n ): Mesub {\n return new Mesub({\n maxRetries: 0,\n ...options,\n apiKey: this.apiKey,\n baseUrl: this.baseUrl,\n fetch: this.fetch,\n webhookSecret: this.webhookSecret,\n });\n }\n\n /**\n * That customer has access to that plan: active and paid, unless `fields`\n * say otherwise. The customer is named as `access` names them, and only\n * that way answers: a wallet granted is not found by its external id.\n */\n grant(customer: Customer | string, plan: string, fields: FakeAccess = {}): AccessAnswer {\n return this.setAccess(customer, plan, {\n access: true,\n status: 'active',\n payment_status: 'paid',\n ...fields,\n });\n }\n\n /** That customer has no access to that plan: `status` says why, `none` by default. */\n deny(customer: Customer | string, plan: string, fields: FakeAccess = {}): AccessAnswer {\n return this.setAccess(customer, plan, { ...fields, access: false });\n }\n\n /**\n * That customer is in the last period of a plan that ends at `endsAt`:\n * active and paid, access until the end and no charge ahead, unless\n * `fields` say otherwise. The plan gets that `ends_at`, and from then on\n * the fake refuses everyone on it, as Mesub does, whatever `status` reads.\n */\n grantLastPeriod(\n customer: Customer | string,\n plan: string,\n endsAt: string | Date,\n fields: FakeAccess = {},\n ): AccessAnswer {\n const end = new Date(endsAt).toISOString();\n\n this.#ends.set(plan, end);\n return this.grant(customer, plan, { access_until: end, ...fields });\n }\n\n /**\n * That plan reached its end, at `at` or now: it takes nobody new, and\n * what the fake holds on it is ended as Mesub ends it, `plan_ended` for a\n * running or late one, `cancelled` for one that had cancelled. Before\n * this is called, a plan past its `ends_at` already refuses everyone,\n * with `status` unchanged: the minutes Mesub takes to end the rows.\n */\n endPlan(plan: string, at: string | Date = new Date()): void {\n this.#ends.set(plan, new Date(at).toISOString());\n\n for (const [key, answer] of this.#answers) {\n if (answer.plan === plan) this.#answers.set(key, { ...answer, ...endedBy(answer) });\n }\n for (const subscription of this.#subscriptions) {\n if (subscription.plan !== plan) continue;\n const ended = endedBy(subscription);\n Object.assign(subscription, ended, 'status' in ended && NO_RETRY);\n }\n }\n\n /**\n * Parks that subscription's seat, as Mesub does when a project holds more\n * subscribers than its tier allows: `paused`, no next charge, access and\n * status unchanged (the fake moves no clock, so the paid period never\n * runs out). With `accessEvents`, raises `subscription.paused`\n * (Mesub-io/backend#509), sent behind the same flag. Throws for an id the\n * fake does not hold.\n */\n park(id: string): ServerSubscription {\n return this.#seat(id, true);\n }\n\n /**\n * Brings a parked seat back: not `paused`, charged again at the end of\n * its period. With `accessEvents`, raises `subscription.unpaused`.\n */\n unpark(id: string): ServerSubscription {\n return this.#seat(id, false);\n }\n\n #seat(id: string, paused: boolean): ServerSubscription {\n const subscription = this.#subscriptions.find((each) => each.id === id);\n if (!subscription) throw new Error(`The fake holds no subscription ${id}.`);\n if (subscription.paused === paused) return this.#view(subscription);\n\n const before = this.#view(subscription).access;\n const next_charge_at = paused ? null : subscription.current_period_end;\n Object.assign(subscription, { paused, next_charge_at });\n this.#answer(subscription, { paused, next_charge_at });\n if (this.#accessEvents) {\n this.#raise(\n paused ? 'subscription.paused' : 'subscription.unpaused',\n subscription,\n before,\n );\n }\n return this.#view(subscription);\n }\n\n /**\n * What `/v1/access` answers that customer on that plan, on top of the\n * answer for a customer with nothing. `wallet` defaults to the wallet\n * asked about, and to null for an external id or an email.\n */\n setAccess(customer: Customer | string, plan: string, fields: FakeAccess): AccessAnswer {\n const asked = customerOf(customer);\n const answer: Held = { ...nothing(asked, plan), ...fields, plan };\n\n this.#answers.set(keyOf(asked, plan), answer);\n return this.#fresh(answer);\n }\n\n /**\n * A subscription `retrieve` and `list` answer, active by default, dated\n * as Mesub dates a row of that `status` unless `fields` say otherwise:\n * active, paid for a period ending in 30 days and charged then; unpaid,\n * that period over, late, with access and a retry tomorrow, or on `free`\n * no access and hand retries until `retry_deadline`; cancelled, access to\n * the end of the period; stopped (`out_of_retries`) or ended (`closed`),\n * no access. Its access answer is left as it is: set it with `grant`.\n * Throws for a wallet Mesub would refuse. Its plan may be one `plans`\n * leaves out: Mesub serves the rows of a plan it no longer has.\n */\n addSubscription(\n fields: Partial<ServerSubscription> & { wallet: string; plan: string },\n ): ServerSubscription {\n if (!BASE58_ADDRESS.test(fields.wallet)) {\n throw new Error(`${fields.wallet} is not a wallet: a base58 Solana address.`);\n }\n const subscription = this.#subscription(fields);\n\n this.#subscriptions.push(subscription);\n return subscription;\n }\n\n /**\n * The pull attempts of that subscription, every one it ever had: what\n * `subscriptions.attempts` answers for it, with their `paid` total, and\n * the five newest through `/v1/access` with `attempts`, whose answer\n * becomes about that subscription. Each is a paid one of 9.99 USDC now,\n * priced 9.99 dollars, not a retry, unless told otherwise; one given\n * another `amount` and no `amount_usd` is not priced. Newest first.\n * Throws for an id the fake does not hold.\n */\n setAttempts(id: string, attempts: Array<Partial<SubscriptionAttempt>>): SubscriptionAttempt[] {\n const subscription = this.#subscriptions.find((each) => each.id === id);\n if (!subscription) throw new Error(`The fake holds no subscription ${id}.`);\n\n const now = new Date().toISOString();\n const filled = attempts\n .map((attempt): SubscriptionAttempt => ({\n id: `att_fake_${(this.#attemptIds += 1)}`,\n attempted_at: now,\n outcome: 'paid',\n reason: null,\n amount: '9990000',\n // The default amount's worth only: no price is guessed for another.\n amount_usd: attempt.amount === undefined ? '9.99' : null,\n signature: 'fake_signature',\n retry: false,\n retry_number: null,\n retries_allowed: null,\n period_start: null,\n ...attempt,\n }))\n // Newest first, as Mesub serves them; as handed within one instant.\n .sort((a, b) => Date.parse(b.attempted_at) - Date.parse(a.attempted_at));\n\n this.#attempts.set(id, filled);\n this.#answer(subscription, {\n subscribed_since: subscription.confirmed_at,\n attempts: filled\n .slice(0, ACCESS_ATTEMPTS)\n .map(({ outcome, reason, amount, attempted_at, signature }): ServedAttempt => ({\n outcome,\n reason,\n amount,\n attempted_at,\n signature,\n })),\n });\n return filled;\n }\n\n /**\n * A webhook of that type, as Mesub posts it to an endpoint: the event's\n * body (Mesub-io/backend#482), on a made-up subscription and detail\n * unless `fields` say otherwise, the subscription the fake holds under\n * `fields.subscription.id` when it holds one, signed with\n * `webhookSecret`. The `test` event is Mesub's test delivery: `test:\n * true`, `evt_test`, on its sample subscription (`sub_test`,\n * `user_123`). Post it to your own handler, or hand it to\n * `webhooks.verify` of a `client()`. Nothing is added to the fake.\n */\n async webhook(type: WebhookEventType, fields: FakeWebhookFields = {}): Promise<SignedWebhook> {\n const now = new Date();\n const test = type === 'test';\n // A test delivery carries Mesub's sample subscription, the same every time.\n const data = test\n ? { ...TEST_SUBSCRIPTION, ...fields.subscription }\n : withClosing(this.#madeUp(type, fields.subscription));\n const sequence =\n fields.sequence === undefined\n ? test\n ? 1\n : this.#nextSequence(data.id)\n : fields.sequence;\n const payload = {\n id: fields.id ?? (test ? TEST_EVENT_ID : this.#nextEventId()),\n object: 'event',\n type,\n created_at: fields.created_at ?? now.toISOString(),\n network: 'devnet',\n api_version: API_VERSION,\n ...(sequence !== null && { sequence }),\n data,\n detail: fields.detail ?? detailOf(type, now, data),\n ...(test && { test: true }),\n };\n\n return signWebhook(payload, {\n secret: this.webhookSecret,\n id: fields.delivery_id ?? `msg_fake_${(this.#deliveryIds += 1)}`,\n ...(fields.timestamp !== undefined && { timestamp: fields.timestamp }),\n });\n }\n\n /**\n * The subscription an event carries: the one the fake holds under\n * `fields.id` when it holds one, else a made-up one, active and paid on\n * `pro` unless the type says otherwise; `fields` on top.\n */\n #madeUp(type: WebhookEventType, fields: Partial<ServerSubscription> = {}): ServerSubscription {\n const held = this.#subscriptions.find((each) => each.id === fields.id);\n const base = held\n ? this.#view(held)\n : {\n id: fields.id ?? `sub_fake_event_${(this.#madeUpIds += 1)}`,\n wallet: FAKE_WALLET,\n plan: 'pro',\n ...subscriptionOf(type),\n };\n\n return this.#subscription({ ...base, ...fields });\n }\n\n /**\n * An event `/v1/events` serves from now on, as Mesub would have raised\n * it: a made-up subscription and detail unless `fields` say otherwise,\n * the subscription the fake holds under `fields.subscription.id` when it\n * holds one. Nothing else changes: what it says happened is not done.\n */\n addEvent(type: EventType, fields: FakeEventFields = {}): MesubEvent {\n const now = new Date();\n const data = withClosing(this.#madeUp(type, fields.subscription));\n const sequence =\n fields.sequence === undefined ? this.#nextSequence(data.id) : fields.sequence;\n const event: MesubEvent = {\n id: fields.id ?? this.#nextEventId(),\n object: 'event',\n type,\n created_at: fields.created_at ?? now.toISOString(),\n network: 'devnet',\n api_version: API_VERSION,\n ...(sequence !== null && { sequence }),\n data,\n detail: (fields.detail ?? detailOf(type, now, data)) as never,\n } as MesubEvent;\n\n this.#events.push(event);\n return structuredClone(event);\n }\n\n /**\n * Every call answers that error from now on, `'outage'` a 503, until\n * `fail(null)`.\n */\n fail(failure: FakeFailure | 'outage' | null): void {\n this.#failure = failure === 'outage' ? OUTAGE : failure;\n }\n\n /**\n * Forgets every answer, subscription, failure and request, and starts\n * every id over: the same test makes the same ids, whatever ran before.\n */\n reset(): void {\n this.#answers.clear();\n this.#ends.clear();\n this.#subscriptions = [];\n this.#built.clear();\n this.#gone.clear();\n this.#attempts.clear();\n this.#events = [];\n this.#sequences.clear();\n this.#handRetries.clear();\n this.#failure = null;\n this.requests.length = 0;\n this.#ids = 0;\n this.#madeUpIds = 0;\n this.#eventIds = 0;\n this.#attemptIds = 0;\n this.#requestIds = 0;\n this.#deliveryIds = 0;\n }\n\n /** A subscription with every field, dated as Mesub dates one of its `status`. */\n #subscription(\n fields: Partial<ServerSubscription> & Pick<ServerSubscription, 'wallet' | 'plan'>,\n ): ServerSubscription {\n const now = Date.now();\n const iso = (ms: number) => new Date(ms).toISOString();\n\n return {\n id: fields.id ?? this.#nextId(),\n status: 'active',\n paused: false,\n end_reason: null,\n late_reason: null,\n stop_reason: null,\n blocked_since: null,\n blocked_reason: null,\n email: null,\n external_id: null,\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n closable: false,\n closable_from: null,\n created_at: iso(now),\n confirmed_at: iso(now),\n ...datesOf(fields.status ?? 'active', this.#tier, now),\n ...fields,\n };\n }\n\n /**\n * An answer as `/v1/access` serves it now: bounded by its plan's end, and\n * fresh for as long as Mesub keeps it, unless a test said otherwise.\n */\n #fresh(answer: Held): AccessAnswer {\n const end = this.#endOf(answer.plan);\n const served = bounded(answer, end);\n const closes = closingOf(served);\n const closed = {\n ...served,\n closable: served.closable ?? closes.closable,\n closable_from:\n served.closable_from === undefined ? closes.closable_from : served.closable_from,\n };\n\n return {\n ...closed,\n revalidate_after:\n served.revalidate_after ?? this.#revalidateAfter ?? revalidateAfter(closed, end),\n };\n }\n\n /**\n * A subscription as Mesub serves it now: bounded by its plan's end, and\n * `closable` as the fake's close would take it.\n */\n #view(subscription: ServerSubscription): ServerSubscription {\n const served = bounded(subscription, this.#endOf(subscription.plan));\n\n return { ...served, ...closingOf(served, this.#gone.has(subscription.id)) };\n }\n\n /**\n * Records the event Mesub raises for a change to that subscription, as\n * it stands now, then, with `accessEvents`, the access event of the same\n * change when access flipped from `before`: the same `data`, one\n * `sequence` later.\n */\n #raise(\n type: EventType,\n subscription: ServerSubscription,\n before: boolean,\n detail: object = {},\n ): void {\n const created_at = new Date().toISOString();\n const data = this.#view(subscription);\n const push = (each: EventType, eachDetail: object) =>\n this.#events.push({\n id: this.#nextEventId(),\n object: 'event',\n type: each,\n created_at,\n network: 'devnet',\n api_version: API_VERSION,\n sequence: this.#nextSequence(subscription.id),\n data: structuredClone(data),\n detail: eachDetail,\n } as MesubEvent);\n\n push(type, detail);\n if (this.#accessEvents && data.access !== before) {\n const flip = data.access\n ? 'subscription.access_granted'\n : 'subscription.access_revoked';\n push(flip, accessDetailOf(data, created_at));\n }\n }\n\n /** The next `sequence` of that subscription's events: 1, 2, 3... */\n #nextSequence(id: string): number {\n const next = (this.#sequences.get(id) ?? 0) + 1;\n this.#sequences.set(id, next);\n return next;\n }\n\n /** A plan's end: the one a test gave it, else its `ends_at` in `plans`. */\n #endOf(slug: string | null): string | null {\n if (slug === null) return null;\n\n return (\n this.#ends.get(slug) ??\n this.#planList.find((each) => each.slug === slug)?.ends_at ??\n null\n );\n }\n\n /** Whether that plan is past its end, where it takes nobody new. */\n #over(slug: string | null): boolean {\n const end = this.#endOf(slug);\n\n return end !== null && Date.parse(end) <= Date.now();\n }\n\n /** A plan as served: with its end, and taking nobody new once past it. */\n #served(plan: Plan): Plan {\n const ends_at = this.#endOf(plan.slug);\n\n return { ...plan, ends_at, available: plan.available && !this.#over(plan.slug) };\n }\n\n #nextId(): string {\n this.#ids += 1;\n return `sub_fake_${this.#ids}`;\n }\n\n #nextEventId(): string {\n this.#eventIds += 1;\n return `evt_fake_${this.#eventIds}`;\n }\n\n async #handle(input: string | URL | Request, init: RequestInit = {}): Promise<Response> {\n const url = new URL(input instanceof Request ? input.url : String(input));\n const base = new URL(this.baseUrl);\n const prefix = base.pathname.replace(/\\/+$/, '');\n\n // Anything else is not this fake: a 404 that is not Mesub's, as a wrong baseUrl gets.\n if (url.origin !== base.origin || !url.pathname.startsWith(`${prefix}/`)) {\n return new Response('Not Found', { status: 404 });\n }\n\n this.#requestIds += 1;\n const id = `req_fake${String(this.#requestIds).padStart(20, '0')}`;\n\n return stamped(await this.#route(url, url.pathname.slice(prefix.length), init), id);\n }\n\n async #route(url: URL, path: string, init: RequestInit): Promise<Response> {\n const method = (init.method ?? 'GET').toUpperCase();\n const headers = new Headers(init.headers);\n const query = Object.fromEntries(url.searchParams);\n const body = typeof init.body === 'string' ? (JSON.parse(init.body) as unknown) : undefined;\n this.requests.push({ method, path, query, headers, body });\n\n // As Mesub's ApiKeyGuard, which tells a missing key from a wrong one.\n const [scheme, key] = (headers.get('authorization') ?? '').split(' ');\n if (scheme?.toLowerCase() !== 'bearer' || !key) {\n return error(401, 'missing_api_key', 'Send your API key as a bearer token.');\n }\n if (key !== this.apiKey) {\n return error(401, 'invalid_api_key', 'That API key is not valid.');\n }\n // After the key, as Mesub's ApiVersionInterceptor: absent is the first\n // version; anything not, to the letter, a version served is refused.\n const version = headers.get(API_VERSION_HEADER);\n if (version !== null && version !== API_VERSION) {\n return error(\n 400,\n 'unsupported_version',\n `${API_VERSION_HEADER} must be one of the versions this API serves: ${API_VERSION}.`,\n );\n }\n if (this.#failure) {\n const { status, code, message, retryable } = this.#failure;\n const retryAfter =\n this.#failure.retryAfter ?? waitOf(method, path, status, code, this.#tier);\n return error(status, code, message ?? `Mesub answered ${status}.`, {\n ...(retryable !== undefined && { retryable }),\n ...(retryAfter !== undefined && { retryAfter }),\n });\n }\n\n if (method === 'GET' && path === '/v1/plans') {\n return Response.json({ plans: this.#planList.map((plan) => this.#served(plan)) });\n }\n const slug = /^\\/v1\\/plans\\/([^/]+)$/.exec(path)?.[1];\n if (method === 'GET' && slug !== undefined) {\n const plan =\n this.#planList.find((each) => each.slug === slug) ??\n // No list given: every slug exists, as for access.\n (this.#plans === null ? fakePlan(slug) : undefined);\n\n return plan\n ? Response.json(this.#served(plan))\n : error(404, 'plan_not_found', `No plan under slug ${slug}`);\n }\n\n if (method === 'GET' && path === '/v1/access') return this.#access(query);\n if (method === 'GET' && path === '/v1/events') return this.#eventList(query);\n const eventId = /^\\/v1\\/events\\/([^/]+)$/.exec(path)?.[1];\n if (method === 'GET' && eventId !== undefined) {\n const event = this.#servedEvents().find(\n (each) => each.id === decodeURIComponent(eventId),\n );\n\n return event\n ? Response.json(event)\n : error(\n 404,\n 'event_not_found',\n 'No event of yours under that id in the last 30 days.',\n );\n }\n if (method === 'GET' && path === '/v1/subscriptions') return this.#list(query);\n if (method === 'POST' && path === '/v1/subscriptions') return this.#create(body);\n\n const [, id, step, confirm] =\n /^\\/v1\\/subscriptions\\/([^/]+)(?:\\/(submit|transaction|cancel|resume|close|attempts|retry)(\\/confirm)?)?$/.exec(\n path,\n ) ?? [];\n // A Mesub without the route knows no such path, whatever the id.\n if (step === 'attempts' && (confirm || !this.#attemptsRoute)) {\n return error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n // Mesub's DTO, before any lookup.\n if (method === 'POST' && step === 'submit' && !confirm) {\n const invalid = invalidSubmit(body);\n if (invalid) return invalid;\n }\n if (id !== undefined) {\n const subscription = this.#subscriptions.find((s) => s.id === decodeURIComponent(id));\n if (!subscription) {\n return error(404, 'subscription_not_found', 'No subscription under that id.');\n }\n if (method === 'GET' && !step) return Response.json(this.#view(subscription));\n if (step === 'attempts') {\n return method === 'GET'\n ? this.#attemptsOf(subscription, query)\n : error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n if (method === 'POST' && step === 'submit' && !confirm) {\n return this.#submit(subscription);\n }\n // A fresh transaction (Mesub-io/backend#427): for a pending one only.\n if (step === 'transaction') {\n return method === 'POST' && !confirm\n ? this.#transaction(subscription)\n : error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n // Pay now (Mesub-io/backend#354): taken for a late one, nothing pulled here.\n if (step === 'retry') {\n return method === 'POST' && !confirm\n ? this.#retry(subscription)\n : error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n if (method === 'POST' && step && step !== 'submit') {\n return confirm\n ? this.#confirm(step as FakeAction, subscription, body)\n : this.#build(step as FakeAction, subscription);\n }\n }\n\n return error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n\n #access(query: Record<string, string>): Response {\n const invalid = invalidContact(query);\n if (invalid) return invalid;\n const asked = askedIn(query);\n if (!asked)\n return error(400, 'invalid_request', 'Name one of wallet, external_id or email.');\n\n const { plan } = query;\n const attempts = query['attempts'] === 'true';\n\n if (plan === undefined) {\n const plans = [...this.#answers.entries()]\n .filter(([key]) => key.startsWith(keyOf(asked, '')))\n .map(([, answer]) => withAttempts(this.#fresh(answer), attempts));\n // As Mesub: stale with the first yes that changes; a no would hold\n // the whole list to 10, so with no yes it is the list's own 10.\n const granted = plans.filter((answer) => answer.access);\n\n return Response.json({\n plans,\n revalidate_after: granted.length\n ? Math.min(...granted.map((answer) => answer.revalidate_after))\n : (this.#revalidateAfter ?? REVALIDATE_MIN_S),\n });\n }\n if (this.#plans && !this.#plans.has(plan)) {\n return error(404, 'plan_not_found', `No plan under slug ${plan}`);\n }\n\n const answer = this.#answers.get(keyOf(asked, plan)) ?? nothing(asked, plan);\n return Response.json(withAttempts(this.#fresh(answer), attempts));\n }\n\n #list(query: Record<string, string>): Response {\n const invalid = invalidContact(query);\n if (invalid) return invalid;\n const asked = askedIn(query);\n if (!asked)\n return error(400, 'invalid_request', 'Name one of wallet, external_id or email.');\n\n const limit = query['limit'] === undefined ? 20 : limitOf(query['limit']);\n if (limit === null) return error(400, 'invalid_request', [LIMIT_MESSAGE]);\n // Newest first. The cursor is a position among every subscription\n // held, whoever it is for, and one not held is a 400, as on Mesub.\n const all = [...this.#subscriptions].reverse();\n const after = query['starting_after'];\n const from = after === undefined ? 0 : all.findIndex((s) => s.id === after) + 1;\n if (after !== undefined && from === 0) {\n return error(400, 'invalid_request', 'starting_after is not a subscription of yours.');\n }\n const data = all\n .slice(from)\n // The comeback that replaced it stands for the customer (Mesub-io/backend#254).\n .filter((s) => s.status !== 'superseded')\n .filter((s) => s[asked.kind] === asked.value)\n .filter((s) => query['plan'] === undefined || s.plan === query['plan']);\n\n const page: ServerSubscriptionList = {\n data: data.slice(0, limit).map((s) => this.#view(s)),\n has_more: data.length > limit,\n };\n return Response.json(page);\n }\n\n /** The events Mesub serves now: within the window and from `available_since`, newest first. */\n #servedEvents(): MesubEvent[] {\n const floor = Math.max(Date.now() - EVENTS_WINDOW_MS, Date.parse(this.#eventsSince));\n\n return this.#events\n .filter((event) => Date.parse(event.created_at) >= floor)\n .sort(\n (a, b) =>\n Date.parse(b.created_at) - Date.parse(a.created_at) ||\n // Within a millisecond, by `sequence`, highest first, as Mesub.\n (b.sequence ?? 0) - (a.sequence ?? 0) ||\n (a.id < b.id ? 1 : a.id > b.id ? -1 : 0),\n );\n }\n\n #eventList(query: Record<string, string>): Response {\n const { type, subscription, starting_after: after } = query;\n const limit = Number(query['limit'] ?? EVENTS_LIMIT);\n const reasons = [\n !Number.isInteger(limit) || limit < 1 || limit > EVENTS_LIMIT_MAX\n ? `limit must be a whole number from 1 to ${EVENTS_LIMIT_MAX}`\n : null,\n type !== undefined && !EVENT_TYPES.includes(type as EventType)\n ? `type is one of ${EVENT_TYPES.join(', ')}`\n : null,\n subscription !== undefined && !RESOURCE_ID.test(subscription)\n ? 'subscription must be a subscription id'\n : null,\n after !== undefined && !RESOURCE_ID.test(after)\n ? 'starting_after must be an event id'\n : null,\n ].filter((reason): reason is string => reason !== null);\n if (reasons.length) return error(400, 'invalid_request', reasons);\n\n // The cursor is a position among every event served, whatever the filters.\n const all = this.#servedEvents();\n const from = after === undefined ? 0 : all.findIndex((each) => each.id === after) + 1;\n if (after !== undefined && from === 0) {\n return error(400, 'invalid_request', 'starting_after is not an event of yours.');\n }\n const data = all\n .slice(from)\n .filter((each) => type === undefined || each.type === type)\n .filter((each) => subscription === undefined || each.data.id === subscription);\n\n const page: EventList = {\n data: data.slice(0, limit),\n has_more: data.length > limit,\n available_since: this.#eventsSince,\n };\n return Response.json(page);\n }\n\n #attemptsOf(subscription: ServerSubscription, query: Record<string, string>): Response {\n const all = this.#attempts.get(subscription.id) ?? [];\n const limit = Number(query['limit'] ?? 20);\n const after = query['starting_after'];\n const from = after === undefined ? 0 : all.findIndex((each) => each.id === after) + 1;\n\n if (!Number.isInteger(limit) || limit < 1 || limit > 100) {\n return error(400, 'invalid_request', 'limit must be a whole number from 1 to 100');\n }\n if (after !== undefined && from === 0) {\n return error(\n 400,\n 'invalid_request',\n 'starting_after is not an attempt of this subscription.',\n );\n }\n\n const paid = all.filter((each) => each.outcome === 'paid');\n const page: SubscriptionAttemptList = {\n data: all.slice(from, from + limit),\n has_more: all.length > from + limit,\n // Over every attempt, not the page; an amount that is no whole number counts for 0.\n paid: {\n count: paid.length,\n amount: paid\n .reduce(\n (sum, each) => sum + (/^\\d+$/.test(each.amount) ? BigInt(each.amount) : 0n),\n 0n,\n )\n .toString(),\n },\n };\n return Response.json(page);\n }\n\n #create(body: unknown): Response {\n const { plan, wallet, email, external_id } = (body ?? {}) as Record<string, unknown>;\n\n if (typeof plan !== 'string' || typeof wallet !== 'string') {\n return error(400, 'invalid_request', 'plan and wallet are required.');\n }\n const invalid = invalidContact({ wallet, email });\n if (invalid) return invalid;\n if (this.#plans && !this.#plans.has(plan)) {\n return error(404, 'plan_not_found', `No plan under slug ${plan}`);\n }\n\n if (this.#over(plan)) return error(409, 'plan_ended', 'This plan has ended.');\n\n const contact = {\n email: typeof email === 'string' ? email.trim().toLowerCase() : null,\n external_id: typeof external_id === 'string' ? external_id.trim() : null,\n };\n const held = this.#subscriptions.filter((s) => s.plan === plan && s.wallet === wallet);\n const refusal = liveRefusal(held);\n if (refusal) return refusal;\n\n // One checkout per customer on a wallet, as Mesub keeps them: the same\n // customer gets its own back, another one never does.\n const waiting = held.find(\n (s) => s.status === 'pending' && holderOf(s) === holderOf(contact),\n );\n if (waiting && contact.email !== null) waiting.email = contact.email;\n\n const subscription =\n waiting ??\n this.addSubscription({\n plan,\n wallet,\n ...contact,\n status: 'pending',\n access: false,\n payment_status: 'none',\n current_period_start: null,\n confirmed_at: null,\n });\n\n return Response.json(handedOut(subscription), { status: 201 });\n }\n\n /**\n * A fresh transaction for a subscription still pending, with the same\n * terms message as `create` gave it: a signature held stays good. Refused\n * as Mesub refuses it otherwise.\n */\n #transaction(subscription: ServerSubscription): Response {\n if (subscription.status !== 'pending') {\n return error(409, 'not_awaiting_signature', 'That subscription awaits no signature.');\n }\n if (this.#over(subscription.plan)) return error(409, 'plan_ended', 'This plan has ended.');\n\n const refusal = liveRefusal(\n this.#subscriptions.filter(\n (s) => s.plan === subscription.plan && s.wallet === subscription.wallet,\n ),\n );\n if (refusal) return refusal;\n\n return Response.json(handedOut(subscription), { status: 201 });\n }\n\n /**\n * Lands at once: the subscription turns active and its customer is granted\n * the plan. The other checkouts waiting on that wallet expire, as on Mesub.\n */\n #submit(subscription: ServerSubscription): Response {\n if (subscription.status !== 'pending') {\n return error(409, 'not_awaiting_signature', 'That subscription awaits no signature.');\n }\n // Read again at the submit: a plan that ended since the create takes nobody.\n if (this.#over(subscription.plan)) return error(409, 'plan_ended', 'This plan has ended.');\n for (const other of this.#subscriptions) {\n const beside = other.plan === subscription.plan && other.wallet === subscription.wallet;\n if (beside && other !== subscription && other.status === 'pending') {\n other.status = 'expired';\n }\n }\n\n const now = new Date();\n const end = new Date(now.getTime() + PERIOD_MS).toISOString();\n // A plan that ends inside the period: access to its end, and no second charge.\n const dates = bounded(\n { access: true, access_until: end, next_charge_at: end, next_retry_at: null },\n this.#endOf(subscription.plan),\n );\n Object.assign(subscription, {\n status: 'active',\n payment_status: 'paid',\n current_period_start: now.toISOString(),\n current_period_end: end,\n confirmed_at: now.toISOString(),\n ...dates,\n } satisfies Partial<ServerSubscription>);\n\n const plan = subscription.plan ?? '';\n const fields: FakeAccess = {\n wallet: subscription.wallet,\n subscription_id: subscription.id,\n subscribed_since: now.toISOString(),\n first_subscribed_at: now.toISOString(),\n current_period_end: end,\n access_until: dates.access_until,\n next_charge_at: dates.next_charge_at,\n };\n this.grant({ wallet: subscription.wallet }, plan, fields);\n if (subscription.external_id) {\n this.grant({ external_id: subscription.external_id }, plan, fields);\n }\n if (subscription.email) this.grant({ email: subscription.email }, plan, fields);\n // Pending, it granted nothing. What it paid, as Mesub-io/backend#509 tells it.\n const terms = this.#planList.find((each) => each.slug === subscription.plan);\n this.#raise('subscription.created', subscription, false, {\n first_payment: {\n amount: terms?.amount ?? '9990000',\n mint: terms?.mint ?? USDC,\n signature: 'fake_signature',\n },\n });\n\n return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n }\n\n /**\n * A hand retry, refused as Mesub's `RetryNowService` refuses it from what\n * the fake holds, in its order: a parked late seat, a row not late, a\n * plan past its end, the wait since the last one (by `tier`, with\n * `Retry-After`), and on Free a `retry_deadline` passed or the three\n * retries spent. Taken, it pulls nothing and the row stays `unpaid`: the\n * fake counts it as one more that failed. What only the chain or a race\n * decides (`insufficient_balance`, `retry_cannot_succeed`,\n * `pull_running`, `subscription_changed`) is answered with `fail()`.\n */\n #retry(subscription: ServerSubscription): Response {\n if (subscription.status === 'unpaid' && subscription.paused) {\n return error(\n 409,\n 'subscription_paused',\n \"This subscription is paused over the merchant's tier limit, so it cannot be retried.\",\n );\n }\n if (subscription.status !== 'unpaid') {\n return error(\n 409,\n 'subscription_not_late',\n 'This subscription is not behind on its payment.',\n );\n }\n if (this.#over(subscription.plan)) {\n return error(\n 409,\n 'plan_ended',\n 'This plan has ended, so this payment can no longer be collected.',\n );\n }\n\n const now = Date.now();\n const free = this.#tier === 'free';\n const fired = this.#handRetries.get(subscription.id) ?? { count: 0, at: 0 };\n const openAt =\n fired.count === 0\n ? 0\n : fired.at +\n (free ? FREE_RETRY_WAIT_MS : PAID_RETRY_WAIT_MS * 2 ** (fired.count - 1));\n if (openAt > now) {\n const minutes = Math.ceil((openAt - now) / 60_000);\n return error(\n 409,\n 'retry_too_soon',\n `Try again in ${minutes} ${minutes === 1 ? 'minute' : 'minutes'}.`,\n { retryAfter: Math.ceil((openAt - now) / 1000) },\n );\n }\n if (free) {\n const deadline = subscription.retry_deadline;\n if (deadline !== null && Date.parse(deadline) <= now) {\n return error(\n 409,\n 'retry_deadline_passed',\n 'The missed period ends too soon to retry.',\n );\n }\n if (fired.count >= FREE_HAND_RETRIES) {\n return error(409, 'retries_spent', 'This subscription has no retries left.');\n }\n }\n\n this.#handRetries.set(subscription.id, { count: fired.count + 1, at: now });\n return Response.json(this.#view(subscription), { status: 202 });\n }\n\n /** The transaction of a cancel, resume or close, or Mesub's refusal of it. */\n #build(action: FakeAction, subscription: ServerSubscription): Response {\n const refusal = refusalOf(action, subscription, this.#gone.has(subscription.id));\n if (refusal) return refusal;\n\n const built = this.#built.get(subscription.id) ?? new Set();\n this.#built.set(subscription.id, built.add(action));\n\n const answer: WalletTransaction = {\n transaction: btoa(`fake ${action} transaction`),\n last_valid_block_height: '0',\n transaction_expires_at: expiresAt(),\n };\n return Response.json(answer, { status: 201 });\n }\n\n /** Lands at once: the subscription moves, and so do its customer's access answers. */\n #confirm(action: FakeAction, subscription: ServerSubscription, body: unknown): Response {\n const { signature } = (body ?? {}) as Record<string, unknown>;\n\n if (typeof signature !== 'string' || signature === '') {\n return error(400, 'invalid_request', 'signature must be a transaction signature.');\n }\n const gone = this.#gone.has(subscription.id);\n // One ended with its plan is ended before its close and after it (#344).\n const done =\n subscription.status === DONE[action] && !(endedWithPlan(subscription) && !gone);\n const refusal = done ? null : refusalOf(action, subscription, gone);\n if (refusal) return refusal;\n if (!this.#built.get(subscription.id)?.has(action)) {\n return error(400, 'nothing_to_confirm', 'There is no transaction to confirm yet.');\n }\n // Confirmed already: answered with the row, as Mesub does.\n if (done) return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n // What was built for another action is spent.\n this.#built.set(subscription.id, new Set([action]));\n const before = this.#view(subscription).access;\n\n const now = new Date().toISOString();\n const end = subscription.current_period_end;\n\n if (action === 'close' && endedWithPlan(subscription)) {\n // As Mesub's confirmClose: its account is gone, and the row stays\n // ended with the reason it ended for. Nothing ends twice.\n this.#gone.add(subscription.id);\n this.#answer(subscription, {});\n return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n }\n if (action === 'cancel') {\n // Paid up, it keeps its access to the end of the period.\n Object.assign(subscription, {\n status: 'cancelled',\n stop_reason: null,\n ...NO_BLOCK,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n access_until: subscription.access ? (subscription.access_until ?? end) : null,\n } satisfies Partial<ServerSubscription>);\n this.#answer(subscription, {\n status: 'cancelled',\n stop_reason: null,\n ...NO_BLOCK,\n // The access a cancellation keeps, also where no grant dated it.\n access_until: subscription.access_until,\n cancelled_at: now,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n });\n } else if (action === 'resume') {\n Object.assign(subscription, {\n status: 'active',\n next_charge_at: end,\n } satisfies Partial<ServerSubscription>);\n this.#answer(subscription, {\n status: 'active',\n cancelled_at: null,\n next_charge_at: end,\n });\n } else {\n Object.assign(subscription, {\n status: 'ended',\n end_reason: 'closed',\n stop_reason: null,\n ...NO_BLOCK,\n access: false,\n payment_status: 'none',\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n } satisfies Partial<ServerSubscription>);\n this.#answer(subscription, {\n access: false,\n status: 'ended',\n end_reason: 'closed',\n stop_reason: null,\n ...NO_BLOCK,\n payment_status: 'none',\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n });\n }\n this.#raise(EVENT_OF[action], subscription, before);\n\n return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n }\n\n /**\n * Changes what `/v1/access` answers a subscription's customer, by wallet,\n * external id and email, on top of what each was answered before.\n */\n #answer(subscription: ServerSubscription, fields: FakeAccess): void {\n const plan = subscription.plan ?? '';\n const customers: Customer[] = [{ wallet: subscription.wallet }];\n if (subscription.external_id) customers.push({ external_id: subscription.external_id });\n if (subscription.email) customers.push({ email: subscription.email });\n\n for (const customer of customers) {\n const before = this.#answers.get(keyOf(customerOf(customer), plan));\n\n this.setAccess(customer, plan, {\n wallet: subscription.wallet,\n access: subscription.access,\n payment_status: subscription.payment_status,\n ...before,\n subscription_id: subscription.id,\n // A row whose close landed has nothing left to close; any\n // other is worked out when served.\n ...(this.#gone.has(subscription.id) && { closable: false, closable_from: null }),\n ...fields,\n });\n }\n }\n}\n\n/**\n * How long Mesub keeps an answer fresh, as the back's `AccessService` works\n * it out: 10 s for a no, which a subscribe may lift any second; for a yes,\n * until the first moment it changes on its own (the next retry, else the\n * next charge, else a cancellation's or a parked period's end; the plan's\n * end; a blocked row's `access_until`), from 10 s to 300 s, and 300 with\n * none ahead.\n */\nfunction revalidateAfter(answer: Omit<AccessAnswer, 'revalidate_after'>, planEnd: string | null) {\n if (!answer.access) return REVALIDATE_MIN_S;\n\n const next = soonest(\n answer.next_retry_at ??\n answer.next_charge_at ??\n soonest(\n answer.paused ? answer.current_period_end : null,\n answer.status === 'cancelled' ? answer.access_until : null,\n ),\n planEnd,\n answer.blocked_since === null ? null : answer.access_until,\n );\n if (next === null) return REVALIDATE_MAX_S;\n\n const seconds = Math.floor((Date.parse(next) - Date.now()) / 1000);\n return Math.min(REVALIDATE_MAX_S, Math.max(REVALIDATE_MIN_S, seconds));\n}\n\n/** The earliest of these dates, null when none is given. */\nfunction soonest(...dates: Array<string | null>): string | null {\n return dates.reduce<string | null>(\n (first, date) =>\n date === null || (first !== null && Date.parse(first) <= Date.parse(date))\n ? first\n : date,\n null,\n );\n}\n\n/** What an access answer and a subscription both say of access and of the pulls ahead. */\ntype Dated = Pick<AccessAnswer, 'access' | 'access_until' | 'next_charge_at' | 'next_retry_at'>;\n\n/**\n * A row as Mesub serves it on a plan that ends at `end`: no access from the\n * end on, whatever its status, and before it no `access_until` later than\n * the end and no pull dated at or after it. The real margin (a pull due in\n * the two minutes before the end is not served either) is left out.\n */\nfunction bounded<T extends Dated>(row: T, end: string | null): T {\n if (end === null || !row.access) return row;\n\n const at = Date.parse(end);\n const none = { next_charge_at: null, next_retry_at: null };\n if (at <= Date.now()) return { ...row, ...none, access: false, access_until: null };\n\n const ahead = (date: string | null) => (date !== null && Date.parse(date) < at ? date : null);\n\n return {\n ...row,\n access_until:\n row.access_until !== null && Date.parse(row.access_until) > at ? end : row.access_until,\n next_charge_at: ahead(row.next_charge_at),\n next_retry_at: ahead(row.next_retry_at),\n };\n}\n\n/** What a row of that status reads as on Mesub, dated from `now`, ms. */\ntype StatusDates = Pick<\n ServerSubscription,\n | 'access'\n | 'payment_status'\n | 'current_period_start'\n | 'current_period_end'\n | 'next_charge_at'\n | 'access_until'\n> &\n Partial<ServerSubscription>;\n\n/**\n * The fields Mesub serves a row of that status with: a period that runs 30\n * days from now on a running or cancelled one, a period just over on a late,\n * stopped or ended one, and none on a checkout. Late, it keeps access to its\n * one retry, tomorrow, except on Free, which grants none to a late row and\n * dates the close of its hand retries instead (`billingDatesOf`).\n */\nfunction datesOf(status: ServerSubscription['status'], tier: FakeTier, now: number): StatusDates {\n const iso = (ms: number) => new Date(ms).toISOString();\n const running = { current_period_start: iso(now), current_period_end: iso(now + PERIOD_MS) };\n // The period that went unpaid, or the last one before the row stopped.\n const behind = { current_period_start: iso(now - PERIOD_MS), current_period_end: iso(now) };\n const nothing = {\n access: false,\n payment_status: 'none',\n next_charge_at: null,\n access_until: null,\n } as const;\n\n switch (status) {\n case 'active':\n return {\n ...running,\n access: true,\n payment_status: 'paid',\n next_charge_at: running.current_period_end,\n access_until: running.current_period_end,\n };\n case 'cancelled':\n return {\n ...running,\n access: true,\n payment_status: 'paid',\n next_charge_at: null,\n access_until: running.current_period_end,\n };\n case 'unpaid': {\n const late = {\n ...behind,\n late_reason: 'insufficient_balance',\n payment_status: 'late',\n next_charge_at: null,\n } as const;\n if (tier === 'free') {\n // The close of hand retries: two minutes before the missed period ends.\n const deadline = iso(now + PERIOD_MS - CLOCK_MARGIN_MS);\n return { ...late, access: false, access_until: null, retry_deadline: deadline };\n }\n const retry = iso(now + DAY_MS);\n return {\n ...late,\n access: true,\n // Its last retry: access stops there if that one fails too.\n access_until: retry,\n next_retry_at: retry,\n next_retry_number: 1,\n retries_allowed: 1,\n };\n }\n case 'stopped':\n return { ...behind, ...nothing, stop_reason: 'out_of_retries' };\n case 'ended':\n return { ...behind, ...nothing, end_reason: 'closed' };\n case 'superseded':\n return { ...behind, ...nothing };\n default:\n // pending, expired, failed: a checkout nothing landed for.\n return {\n ...nothing,\n current_period_start: null,\n current_period_end: null,\n confirmed_at: null,\n };\n }\n}\n\n/** About half a minute from now, as Mesub dates a transaction's `last_valid_block_height`. */\nfunction expiresAt(): string {\n return new Date(Date.now() + TRANSACTION_LIFE_MS).toISOString();\n}\n\n/** What `create` and `transaction` hand out for a pending subscription: nothing a chain would take. */\nfunction handedOut(subscription: ServerSubscription): SubscribeTransaction {\n const zero = '0';\n\n return {\n subscription: { id: subscription.id, status: 'pending' },\n transaction: btoa('fake transaction'),\n last_valid_block_height: zero,\n transaction_expires_at: expiresAt(),\n costs: {\n rent: { subscription: zero, authority: null, total: zero },\n fee: { signatures: 2, per_signature: zero, priority: zero, total: zero },\n total: zero,\n },\n terms: {\n message: `Fake Mesub terms: ${subscription.wallet} subscribes to ${subscription.plan}.`,\n expires_at: new Date(Date.now() + 5 * 60_000).toISOString(),\n },\n };\n}\n\n/** A block is only ever on a running row: gone once it is cancelled or over. */\nconst NO_BLOCK = { blocked_since: null, blocked_reason: null } as const;\n\n/** What only a subscription carries of a retry, gone with the retry. */\nconst NO_RETRY = { next_retry_number: null, retries_allowed: null } as const;\n\n/** What a plan's end makes of a row held on it; nothing of one that was over already. */\nfunction endedBy(row: {\n status: ServerSubscription['status'] | AccessAnswer['status'];\n}): Partial<Omit<AccessAnswer, 'status'>> & { status?: 'ended' } {\n const running = row.status === 'active' || row.status === 'unpaid';\n if (!running && row.status !== 'cancelled') return {};\n\n return {\n status: 'ended',\n end_reason: running ? 'plan_ended' : 'cancelled',\n late_reason: null,\n ...NO_BLOCK,\n paused: false,\n access: false,\n payment_status: 'none',\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n };\n}\n\n/** What stops, takes back or closes a subscription in the fake. */\ntype FakeAction = 'cancel' | 'resume' | 'close';\n\n/** The event each confirmed action raises. */\nconst EVENT_OF: Record<FakeAction, EventType> = {\n cancel: 'subscription.cancelled',\n resume: 'subscription.resumed',\n close: 'subscription.ended',\n};\n\n/** What decides a close, on a subscription and on an access answer alike. */\ntype Closing = Pick<ServerSubscription, 'access_until' | 'end_reason' | 'stop_reason'> & {\n status: ServerSubscription['status'] | AccessAnswer['status'];\n};\n\n/**\n * Whether the fake's close would take it now (`refusalOf`), and from when\n * when it would not yet: the end of a cancellation's period.\n */\nfunction closingOf(\n row: Closing,\n gone = false,\n): Pick<ServerSubscription, 'closable' | 'closable_from'> {\n const closable = refusalOf('close', row, gone) === null;\n const ahead = row.access_until !== null && Date.parse(row.access_until) > Date.now();\n\n return {\n closable,\n closable_from: !closable && row.status === 'cancelled' && ahead ? row.access_until : null,\n };\n}\n\n/** A subscription with `closable` as the fake's close would take it. */\nfunction withClosing(subscription: ServerSubscription): ServerSubscription {\n return { ...subscription, ...closingOf(subscription) };\n}\n\n/** The status each action leaves a subscription in. */\nconst DONE: Record<FakeAction, ServerSubscription['status']> = {\n cancel: 'cancelled',\n resume: 'active',\n close: 'ended',\n};\n\n/**\n * Mesub's 409 for an action a subscription's status does not allow, or null.\n * `gone`: a row ended with its plan whose close landed already.\n */\nfunction refusalOf(action: FakeAction, subscription: Closing, gone = false): Response | null {\n const { status, access_until } = subscription;\n const over = access_until === null || Date.parse(access_until) <= Date.now();\n const cancelled =\n status === 'cancelled' || (status === 'ended' && subscription.end_reason === 'cancelled');\n\n if (action === 'cancel') {\n if (status === 'cancelled') {\n return error(409, 'subscription_cancelled', 'This subscription is cancelled already.');\n }\n return ['active', 'unpaid', 'stopped'].includes(status)\n ? null\n : error(409, 'subscription_not_active', 'This subscription is not running.');\n }\n if (action === 'resume') {\n if (status !== 'cancelled') {\n return error(409, 'subscription_not_cancelled', 'This subscription is not cancelled.');\n }\n return over\n ? error(409, 'subscription_ended', 'The period paid for is over: subscribe again.')\n : null;\n }\n // A close, as Mesub's buildCloseTransaction answers it. Ended with its\n // plan, the delegation still holds the deposit (#344): it closes, once.\n if (endedWithPlan(subscription)) {\n return gone\n ? error(\n 409,\n 'subscription_not_on_chain',\n 'This subscription was closed outside Mesub, so it has ended.',\n )\n : null;\n }\n if (isClosed(subscription)) {\n return error(409, 'subscription_not_on_chain', 'This subscription is closed already.');\n }\n if (status === 'ended' && !cancelled) {\n return error(\n 409,\n 'subscription_not_on_chain',\n 'This subscription has ended and cannot be closed through Mesub.',\n );\n }\n // Its authority closed for good (Mesub-io/backend#394): closed at once,\n // nothing to cancel first.\n if (status === 'stopped' && subscription.stop_reason === 'authority_closed') return null;\n // Any other stopped one is one nothing cancelled, which Mesub cancels\n // first: a cancellation signed without Mesub is not modelled here.\n if (!cancelled) {\n return error(\n 409,\n 'subscription_not_cancelled',\n 'Cancel this subscription before closing it.',\n );\n }\n return over\n ? null\n : error(409, 'close_too_early', `This subscription runs until ${access_until}.`);\n}\n\n/**\n * Mesub's 409 for a checkout beside the subscription a wallet holds on the\n * plan, by its status, as the back's `liveRefusal` words it; null when it\n * holds none. A cancelled one past its end is held until it is closed: it\n * reads `ended`, with `end_reason` `cancelled`. A stopped one is not: the\n * fake's is one nothing cancelled, which the wallet comes back over\n * (Mesub-io/backend#211).\n */\nfunction liveRefusal(held: ServerSubscription[]): Response | null {\n const live = held.find(\n (s) =>\n ['active', 'cancelled', 'unpaid'].includes(s.status) ||\n (s.status === 'ended' && s.end_reason === 'cancelled'),\n );\n\n // Its authority closed for good: no comeback goes through it (Mesub-io/backend#394).\n const dead = held.find((s) => s.status === 'stopped' && s.stop_reason === 'authority_closed');\n if (dead && !live) {\n return error(\n 409,\n 'subscription_stopped',\n \"Your wallet's approval for this token was closed for good, so this subscription cannot restart. Close it, then subscribe again.\",\n );\n }\n\n if (!live) return null;\n if (live.status === 'active') {\n return error(409, 'already_subscribed', 'You are already subscribed to this plan.');\n }\n if (live.status === 'unpaid') {\n return error(\n 409,\n 'payment_pending',\n 'You already have a subscription to this plan, and its last payment is late. Open it in your subscriptions to see how to fix it, or cancel it.',\n );\n }\n const until = live.status === 'cancelled' ? live.access_until : null;\n return error(\n 409,\n 'subscription_cancelled',\n until !== null && Date.parse(until) > Date.now()\n ? `Your subscription to this plan is cancelled but runs until ${until}: resume it instead of subscribing again.`\n : 'Your cancelled subscription to this plan must be closed first: close it, then subscribe again.',\n );\n}\n\n/** Whose checkout it is, as Mesub tells them apart: the external id, else the email, else nobody. */\nfunction holderOf(contact: Pick<ServerSubscription, 'email' | 'external_id'>): string {\n if (contact.external_id) return `x:${contact.external_id}`;\n\n return contact.email ? `e:${contact.email}` : '-';\n}\n\n/** The reasons a row ends with its plan: removed, replaced, or past its end. */\nconst WITH_ITS_PLAN: Array<ServerSubscription['end_reason']> = [\n 'plan_removed',\n 'plan_replaced',\n 'plan_ended',\n];\n\n/** A row ended with its plan, which Mesub still closes (Mesub-io/backend#344). */\nfunction endedWithPlan(subscription: Closing): boolean {\n return subscription.status === 'ended' && WITH_ITS_PLAN.includes(subscription.end_reason);\n}\n\nfunction isClosed(subscription: Closing): boolean {\n return subscription.status === 'ended' && subscription.end_reason === 'closed';\n}\n\nfunction keyOf(asked: Asked, plan: string): string {\n return `${asked.kind}:${asked.value}\\n${plan}`;\n}\n\n/** Only `signWebhook`'s own: a fake's deliveries are numbered by the fake, apart. */\nlet signedIds = 0;\n\n/**\n * Signs a body as Mesub does (Standard Webhooks, HMAC-SHA256 of\n * `id.timestamp.body`), for a webhook of your own making: an object is sent\n * as its JSON, a string as is. `FakeMesub.webhook` builds the body for you.\n */\nexport async function signWebhook(\n payload: unknown,\n options: SignWebhookOptions,\n): Promise<SignedWebhook> {\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload);\n const id = options.id ?? `msg_signed_${(signedIds += 1)}`;\n const timestamp = options.timestamp ?? Math.floor(Date.now() / 1000);\n\n return { body, headers: await signedHeaders(options.secret, id, timestamp, body) };\n}\n\n/** A plausible detail for each type that carries one, as the back writes it. */\nfunction detailOf(\n type: WebhookEventType,\n now: Date,\n data: ServerSubscription,\n): Record<string, unknown> {\n const start = now.toISOString();\n const end = new Date(now.getTime() + PERIOD_MS).toISOString();\n const paid = { amount: '9990000', mint: USDC, period_start: start, period_end: end };\n\n switch (type) {\n case 'subscription.renewal_upcoming':\n return {\n can_pay: true,\n renewal_issue: null,\n amount: paid.amount,\n mint: USDC,\n // The check's window opens three days before the charge at most.\n due_at: new Date(now.getTime() + 3 * 24 * 3600 * 1000).toISOString(),\n };\n case 'subscription.renewed':\n return { ...paid, signature: 'fake_signature' };\n case 'subscription.payment_failed':\n return {\n ...paid,\n reason: 'insufficient_balance',\n next_retry_at: new Date(now.getTime() + 24 * 3600 * 1000).toISOString(),\n retry_deadline: null,\n retries_left: 1,\n retry_mode: 'scheduled',\n };\n case 'subscription.stopped':\n return { reason: 'insufficient_balance' };\n case 'subscription.access_granted':\n case 'subscription.access_revoked':\n return accessDetailOf(data, start);\n case 'subscription.created':\n return {\n first_payment: { amount: paid.amount, mint: USDC, signature: 'fake_signature' },\n };\n case 'subscription.ended':\n // A cancellation reaching its end says when (Mesub-io/backend#509).\n return data.end_reason === 'cancelled'\n ? { changed_at: start, end_reason: 'cancelled' }\n : {};\n default:\n return {};\n }\n}\n\n/**\n * An access event's detail as Mesub writes it (`accessDetail` and\n * `accessCauseOf`, Mesub-io/backend#511): when, the hold on its charges, and\n * the date access ends. A grant names that date only on a held subscription;\n * a revocation names the date that passed (a held one's `access_until`, the\n * end of a cancellation or of the plan), and none for a stop or a close.\n */\nfunction accessDetailOf(data: ServerSubscription, changedAt: string): Record<string, unknown> {\n const { access, blocked_reason, access_until, status, end_reason } = data;\n const dated =\n blocked_reason !== null ||\n (status === 'ended' && (end_reason === 'cancelled' || end_reason === 'plan_ended'));\n\n return {\n changed_at: changedAt,\n blocked_reason,\n access_until: access\n ? blocked_reason === null\n ? null\n : access_until\n : (access_until ?? (dated ? changedAt : null)),\n };\n}\n\n/**\n * The made-up subscription a type carries unless a test says otherwise: an\n * active, paid one, but for `access_revoked`, whose `data.access` is false:\n * a cancellation that reached its end.\n */\nfunction subscriptionOf(type: WebhookEventType): Partial<ServerSubscription> {\n if (type === 'subscription.paused') return { paused: true, next_charge_at: null };\n if (type !== 'subscription.access_revoked') return {};\n\n return {\n status: 'ended',\n end_reason: 'cancelled',\n access: false,\n payment_status: 'none',\n next_charge_at: null,\n access_until: null,\n };\n}\n\nfunction nothing(asked: Asked, plan: string): Held {\n return { wallet: asked.kind === 'wallet' ? asked.value : null, plan, ...NOTHING };\n}\n\n/** A Solana address's shape, as Mesub's DTOs hold a wallet to: base58, 32 to 44 characters. */\nconst BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n\n/**\n * Close to the `IsEmail` Mesub checks an email with, validator.js's\n * defaults: no space, one @, and a domain ending in a top-level one of two\n * letters or more, so `ops@localhost` is refused. Not every edge of it.\n */\nconst EMAIL = /^[^\\s@]+@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z]{2,}$/;\n\n/**\n * Mesub's 400 for a wallet or an email its DTOs refuse (`CustomerQueryDto`,\n * `ServerSubscribeDto`), before anything is looked up; null for those it\n * takes, or that are not given. The email is trimmed and lowercased first,\n * as Mesub does.\n */\nfunction invalidContact({ wallet, email }: { wallet?: unknown; email?: unknown }): Response | null {\n const reasons: string[] = [];\n if (typeof wallet === 'string' && !BASE58_ADDRESS.test(wallet)) {\n reasons.push('wallet must be a base58 Solana address');\n }\n if (typeof email === 'string') {\n const normalised = email.trim().toLowerCase();\n if (!EMAIL.test(normalised)) reasons.push('email must be an email address');\n if (normalised.length > 254) {\n reasons.push('email must be shorter than or equal to 254 characters');\n }\n }\n\n return reasons.length ? error(400, 'invalid_request', reasons) : null;\n}\n\nconst LIMIT_MESSAGE = 'limit must be a whole number from 1 to 100';\n\n/** A list's `limit` as Mesub reads it (`asInteger`, then 1 to 100), null when it refuses it. */\nfunction limitOf(value: string): number | null {\n const limit = /^\\d{1,4}$/.test(value) ? Number(value) : NaN;\n\n return limit >= 1 && limit <= 100 ? limit : null;\n}\n\n/**\n * Mesub's 400 for a submit body its `ServerSubmitDto` refuses, null for one\n * it takes. The transaction is held to base64, as there; `terms_signature`\n * only to a string that is not empty, where Mesub holds it to 64 bytes in\n * base58: the fake checks no signature, so a test may hand it any.\n */\nfunction invalidSubmit(body: unknown): Response | null {\n const { transaction, terms_signature } = (body ?? {}) as Record<string, unknown>;\n const reasons: string[] = [];\n\n if (typeof transaction !== 'string' || transaction.length > 2048) {\n reasons.push('transaction must be shorter than or equal to 2048 characters');\n }\n if (typeof transaction !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(transaction)) {\n reasons.push('transaction must be base64');\n }\n if (typeof terms_signature !== 'string' || terms_signature === '') {\n reasons.push('terms_signature must be a base58 signature');\n }\n\n return reasons.length ? error(400, 'invalid_request', reasons) : null;\n}\n\n/** The one customer a query names, or null when it names none or several. */\nfunction askedIn(query: Record<string, string>): Asked | null {\n try {\n return customerOf({\n wallet: query['wallet'],\n external_id: query['external_id'],\n email: query['email'],\n } as unknown as Customer);\n } catch {\n return null;\n }\n}\n\nfunction withAttempts(answer: AccessAnswer, attempts: boolean): AccessAnswer {\n if (attempts) return { ...answer, attempts: answer.attempts ?? [] };\n\n const light = { ...answer };\n delete light.attempts;\n return light;\n}\n\n/**\n * A plan as `/v1/plans` serves one: 9.99 USDC a month on devnet, unless told\n * otherwise. Its token follows its `symbol`, which only a vouched mint has:\n * verified and worth its amount in dollars with one, unverified and unpriced\n * with a `token` given or a null `symbol`.\n */\nfunction fakePlan(slug: string, { token, ...over }: Omit<FakePlanFields, 'slug'> = {}): Plan {\n const symbol = over.symbol === undefined ? (token ? null : 'USDC') : over.symbol;\n const vouched = token === undefined && symbol !== null;\n\n return {\n slug,\n name: slug,\n description: null,\n project_name: 'Test project',\n logo_url: null,\n amount: '9990000',\n amount_display: '9.99',\n amount_usd: vouched ? (over.amount_display ?? '9.99') : null,\n decimals: 6,\n symbol,\n token: {\n symbol: token ? null : symbol,\n name: vouched && symbol === 'USDC' ? 'USD Coin' : null,\n logo_url: null,\n verified: vouched,\n ...token,\n },\n mint: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',\n period_hours: 720,\n network: 'devnet',\n status: 'active',\n available: true,\n ends_at: null,\n ...over,\n };\n}\n\n/**\n * The codes Mesub answers `retryable: true`, from the back's `ERROR_CODES`,\n * which sets the flag by code alone. Every other code it has is false, and\n * none of those is ever answered as a 408, a 429 or a 5xx, so the status\n * says the same of them.\n */\nconst RETRYABLE_CODES = new Set([\n 'rate_limited',\n 'internal_error',\n 'unavailable',\n 'comeback_in_flight',\n 'comeback_period_rolling',\n 'pending_cap_reached',\n 'subscription_changed',\n 'retry_too_soon',\n 'pull_running',\n 'network_unavailable',\n]);\n\n/**\n * The `Retry-After` Mesub sends with a 503 on a route that builds, sends or\n * queues: its spec makes it required there. The seat wait of a submit\n * (`SEAT_BUSY_RETRY_AFTER`), the queue of a retry (`UNQUEUED_RETRY_AFTER`),\n * the chain guard's otherwise. And the one `retry_too_soon` always carries.\n * Undefined elsewhere.\n */\nfunction waitOf(\n method: string,\n path: string,\n status: number,\n code: string,\n tier: FakeTier,\n): number | undefined {\n // Always sent with its wait: the first one of the tier, as no retry was counted.\n if (code === 'retry_too_soon')\n return (tier === 'free' ? FREE_RETRY_WAIT_MS : PAID_RETRY_WAIT_MS) / 1000;\n if (status !== 503 || method !== 'POST' || !path.startsWith('/v1/subscriptions')) {\n return undefined;\n }\n if (code === 'unavailable' && path.endsWith('/submit')) return 2;\n if (code === 'unavailable' && path.endsWith('/retry')) return 20;\n return 10;\n}\n\n/** An error as the back writes it: Nest's body, plus `code` and `retryable`. */\nfunction error(\n status: number,\n code: string,\n // A list for what Mesub's validation refused, as Nest answers it.\n message: string | string[],\n { retryable, retryAfter }: { retryable?: boolean; retryAfter?: number } = {},\n): Response {\n return Response.json(\n {\n statusCode: status,\n error: REASONS[status] ?? 'Error',\n message,\n code,\n retryable:\n retryable ??\n (RETRYABLE_CODES.has(code) || status === 408 || status === 429 || status >= 500),\n },\n {\n status,\n ...(retryAfter !== undefined && { headers: { 'Retry-After': String(retryAfter) } }),\n },\n );\n}\n\n/**\n * The answer with the id Mesub gives every request (Mesub-io/backend#472): in\n * `Mesub-Request-Id`, and in an error body's `request_id`. And the version it\n * is written in, `Mesub-Version`, on every answer, errors included: the one\n * served, also for a call that named none or one not served.\n */\nasync function stamped(response: Response, id: string): Promise<Response> {\n const headers = new Headers(response.headers);\n headers.set(REQUEST_ID_HEADER, id);\n headers.set(API_VERSION_HEADER, API_VERSION);\n\n if (response.ok) return new Response(response.body, { status: response.status, headers });\n\n const body = (await response.json()) as Record<string, unknown>;\n return Response.json({ ...body, request_id: id }, { status: response.status, headers });\n}\n"],"mappings":"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","names":["answer","nothing"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["/**\n * `@mesub/node/testing`: a fake Mesub, for a merchant's own tests. It answers\n * what the SDK calls (`/v1/access`, `/v1/plans`, `/v1/subscriptions`, its\n * fresh transaction, its attempts and its cancel, resume and close, and\n * `/v1/events`) from what the test sets, through a `fetch` handed to the\n * client: no network, no backend, no test framework of its own.\n *\n * ```ts\n * import { FakeMesub } from '@mesub/node/testing';\n *\n * const fake = new FakeMesub();\n * fake.grant(wallet, 'pro');\n * const mesub = fake.client();\n * await mesub.hasAccess(wallet, 'pro'); // true\n *\n * const { body, headers } = await fake.webhook('subscription.renewed');\n * await mesub.webhooks.verify(body, headers); // the event\n * ```\n */\nimport type { AccessAnswer, Customer, ServedAttempt } from './answer.js';\nimport { Mesub, type MesubOptions } from './client.js';\nimport { type Asked, customerOf } from './customer.js';\nimport type { EventList } from './events.js';\nimport type { Plan, PlanToken } from './plans.js';\nimport type {\n ServerSubscription,\n ServerSubscriptionList,\n SubscribeTransaction,\n SubscriptionAttempt,\n SubscriptionAttemptList,\n WalletTransaction,\n} from './subscriptions.js';\nimport { API_VERSION, API_VERSION_HEADER } from './version.js';\nimport {\n type EventType,\n type MesubEvent,\n signedHeaders,\n type WebhookEventType,\n} from './webhooks.js';\n\nexport interface FakeMesubOptions {\n /** Where the fake answers. Defaults to `https://api.mesub.test`. */\n baseUrl?: string;\n /** The only key it accepts; any other is answered 401. Defaults to `SUB_test_fake`. */\n apiKey?: string;\n /**\n * The plans that exist: any other slug is answered 404 `plan_not_found`.\n * By default every slug exists. A slug alone, or the fields `plans.list`\n * and `plans.retrieve` should answer for it: the rest is filled in.\n * Name them to test the widget routes, which read `plans.list`: left\n * out, the list is empty. A plan given an `ends_at` is served as Mesub\n * serves one: no `access_until` later than it, no pull dated from it\n * on, and no access once it has passed. A plan's token is USDC, verified,\n * and its `amount_usd` its `amount_display`: give a `token` (some of its\n * fields are enough) and an `amount_usd` for any other, such as an\n * unverified one with a null `symbol`.\n */\n plans?: Array<string | FakePlanFields>;\n /** What `webhook()` signs with, and `client()` verifies with. Defaults to a fixed `whsec_` secret. */\n webhookSecret?: string;\n /**\n * False to act as a Mesub that predates\n * `GET /v1/subscriptions/:id/attempts`: it answers Nest's 404 for a route\n * it does not have, and attempts are only served through `/v1/access`.\n */\n attemptsRoute?: boolean;\n /**\n * Mesub's `available_since`: events created before it are not served on\n * `/v1/events`. Defaults to 30 days before the fake was made, so every\n * event of the window is.\n */\n eventsSince?: string | Date;\n /**\n * The `revalidate_after` of every `/v1/access` answer, in seconds: 0 for\n * a test that wants each change seen on the next call of the same\n * client. Left out, Mesub's own: 10 for a no, and for a yes the time to\n * its next charge, retry or end, from 10 to 300, so a client keeps a yes\n * up to five minutes. One set on an answer with `setAccess` wins.\n */\n revalidateAfter?: number;\n /**\n * True to raise `subscription.access_granted` and\n * `subscription.access_revoked` (Mesub-io/backend#507) when a change the\n * fake makes flips access, right after the event that says why, one\n * `sequence` later: a submit grants. False by default, as on Mesub until\n * it turns them on. The fake moves no clock, so a date\n * passing revokes nothing: record that with `addEvent`, which, like\n * `webhook`, makes either type whatever this says.\n */\n accessEvents?: boolean;\n /**\n * The project's tier, `dev` by default. It decides what Pay now\n * (`subscriptions.retry`) refuses, as on Mesub: on `free` a wait of 10\n * minutes after each hand retry, three per missed period, none past\n * `retry_deadline`; on `dev` and `business` none for the first, then a\n * wait of 20 minutes doubling after each. And what `addSubscription`\n * gives a late row: no access on `free`, access through its retries\n * otherwise.\n */\n tier?: FakeTier;\n}\n\n/** A project's tier, as Mesub names them. */\nexport type FakeTier = 'free' | 'dev' | 'business';\n\n/** A plan as declared to the fake: its slug, and any field to set, a token's included. */\nexport type FakePlanFields = Partial<Omit<Plan, 'token'>> & {\n slug: string;\n token?: Partial<PlanToken>;\n};\n\n/** How many attempts `/v1/access` serves at most. */\nconst ACCESS_ATTEMPTS = 5;\n\n/** A webhook as Mesub posts it: the exact body, and the three headers that sign it. */\nexport interface SignedWebhook {\n body: string;\n headers: Record<string, string>;\n}\n\nexport interface SignWebhookOptions {\n /** The endpoint's secret, `whsec_...`. */\n secret: string;\n /**\n * `webhook-id`. Defaults to a new `msg_signed_...` each time, never one\n * a fake's `webhook()` gives.\n */\n id?: string;\n /** `webhook-timestamp`, Unix seconds. Defaults to now. */\n timestamp?: number;\n}\n\n/** What `FakeMesub.addEvent` records, on top of a made-up event of that type. */\nexport interface FakeEventFields {\n /**\n * Fields of the subscription it carries, on top of the one the fake\n * holds under that `id`, else of an active, paid one on `pro`.\n */\n subscription?: Partial<ServerSubscription>;\n /** The event's detail. Defaults to a plausible one for the type. */\n detail?: Record<string, unknown>;\n /** When the event happened. Defaults to now. */\n created_at?: string;\n /** The event's id. Defaults to a new `evt_fake_...`. */\n id?: string;\n /**\n * Its place among its subscription's events. Defaults to the next one\n * for that subscription; null leaves it out, as on an event Mesub could\n * not number.\n */\n sequence?: number | null;\n}\n\n/** What `FakeMesub.webhook` sends, on top of a made-up event of that type. */\nexport interface FakeWebhookFields {\n /**\n * Fields of the subscription it carries, on top of the one the fake\n * holds under that `id`, else of an active, paid one on `pro`. The\n * `test` event carries Mesub's sample one (`sub_test`, `user_123`).\n */\n subscription?: Partial<ServerSubscription>;\n /** The event's detail. Defaults to a plausible one for the type. */\n detail?: Record<string, unknown>;\n /** When the event happened. Defaults to now. */\n created_at?: string;\n /**\n * The event's id. Defaults to a new `evt_fake_...`, and to `evt_test` for\n * the `test` event, as Mesub's test deliveries carry.\n */\n id?: string;\n /**\n * Its place among its subscription's events. Defaults to the next one\n * for that subscription, and to 1 on the `test` event, as Mesub's test\n * deliveries carry; null leaves it out, as on an event Mesub could not\n * number.\n */\n sequence?: number | null;\n /**\n * The delivery's id, the `webhook-id` header. Defaults to the fake's\n * next `msg_fake_...`, from `msg_fake_1`, and again after `reset()`.\n */\n delivery_id?: string;\n /** `webhook-timestamp`, Unix seconds. Defaults to now. */\n timestamp?: number;\n}\n\n/** One call the fake received. */\nexport interface FakeRequest {\n method: string;\n /** Under the base URL, e.g. `/v1/access`. */\n path: string;\n query: Record<string, string>;\n headers: Headers;\n /** The parsed JSON body of a POST, undefined otherwise. */\n body: unknown;\n}\n\n/** An error the fake answers instead, as Mesub would send it. */\nexport interface FakeFailure {\n status: number;\n /** Mesub's own code, e.g. `network_unavailable`. */\n code: string;\n message?: string;\n /**\n * Defaults to what Mesub says of the code, e.g. true for\n * `subscription_changed`; for a code Mesub does not have, to what the\n * status says: true for 408, 429 and 5xx.\n */\n retryable?: boolean;\n /**\n * Seconds, sent as `Retry-After`. A 503 on a route that builds, sends or\n * queues (every POST under `/v1/subscriptions`) defaults to Mesub's own:\n * 2 for `unavailable` on submit (too many submits waiting), 20 for\n * `unavailable` on retry (the charge could not be queued), 10 otherwise.\n * `retry_too_soon` defaults to the first wait of the fake's `tier`: 600\n * on `free`, 1200 on `dev` and `business`.\n */\n retryAfter?: number;\n}\n\n/** What a test may set of an access answer: all but the plan, named apart. */\nexport type FakeAccess = Partial<Omit<AccessAnswer, 'plan'>>;\n\n/**\n * An access answer as the fake holds it: `revalidate_after`, `closable` and\n * `closable_from` only when a test set them, worked out when served otherwise.\n */\ntype Held = Omit<AccessAnswer, 'revalidate_after' | 'closable' | 'closable_from'> &\n Partial<Pick<AccessAnswer, 'revalidate_after' | 'closable' | 'closable_from'>>;\n\n/** What Mesub answers a customer with nothing on a plan. */\nconst NOTHING: Omit<Held, 'wallet' | 'plan'> = {\n subscription_id: null,\n access: false,\n status: 'none',\n paused: false,\n end_reason: null,\n late_reason: null,\n stop_reason: null,\n payment_status: 'none',\n blocked_since: null,\n blocked_reason: null,\n subscribed_since: null,\n first_subscribed_at: null,\n current_period_end: null,\n cancelled_at: null,\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n};\n\n/** The freshest and the stalest Mesub serves an answer, in seconds (the back's `AccessService`). */\nconst REVALIDATE_MIN_S = 10;\nconst REVALIDATE_MAX_S = 300;\n\nconst OUTAGE: FakeFailure = {\n status: 503,\n code: 'unavailable',\n message: 'Mesub is unavailable.',\n};\n\n/** 24 bytes, as Mesub's secrets are. Only ever a test's. */\nconst FAKE_WEBHOOK_SECRET = 'whsec_bWVzdWItZmFrZS13ZWJob29rLWtleS0x';\nconst FAKE_WALLET = '11111111111111111111111111111111';\n/** The id every test delivery carries, as Mesub's: no event stands behind it. */\nconst TEST_EVENT_ID = 'evt_test';\n/** How far back `/v1/events` reaches, as on Mesub. */\nconst EVENTS_WINDOW_MS = 30 * 24 * 3600 * 1000;\n/** What `/v1/events` serves per page, by default and at most. */\nconst EVENTS_LIMIT = 25;\nconst EVENTS_LIMIT_MAX = 100;\n/** The v1 event types, as Mesub names them and `type` is checked against. */\nconst EVENT_TYPES: readonly EventType[] = [\n 'subscription.created',\n 'subscription.renewal_upcoming',\n 'subscription.renewed',\n 'subscription.payment_failed',\n 'subscription.stopped',\n 'subscription.cancelled',\n 'subscription.resumed',\n 'subscription.ended',\n 'subscription.expired',\n 'subscription.access_granted',\n 'subscription.access_revoked',\n 'subscription.paused',\n 'subscription.unpaused',\n];\n/** An id as Mesub's DTOs take one. */\nconst RESOURCE_ID = /^[A-Za-z0-9_-]{1,64}$/;\n/** The header Mesub names every request in (Mesub-io/backend#472). */\nconst REQUEST_ID_HEADER = 'Mesub-Request-Id';\nconst USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\nconst PERIOD_MS = 30 * 24 * 3600 * 1000;\nconst DAY_MS = 24 * 3600 * 1000;\n/** The id and the made-up subscription of every test delivery, as Mesub's (`TEST_SUBSCRIPTION`). */\nconst TEST_SUBSCRIPTION: ServerSubscription = {\n id: 'sub_test',\n status: 'active',\n paused: false,\n end_reason: null,\n late_reason: null,\n stop_reason: null,\n access: true,\n payment_status: 'paid',\n blocked_since: null,\n blocked_reason: null,\n plan: 'pro',\n wallet: FAKE_WALLET,\n email: 'subscriber@example.com',\n external_id: 'user_123',\n current_period_start: '2026-01-01T00:00:00.000Z',\n current_period_end: '2026-01-31T00:00:00.000Z',\n next_charge_at: '2026-01-31T00:00:00.000Z',\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n access_until: '2026-01-31T00:00:00.000Z',\n closable: false,\n closable_from: null,\n created_at: '2026-01-01T00:00:00.000Z',\n confirmed_at: '2026-01-01T00:00:00.000Z',\n};\n/** Free: the wait after each hand retry (`MANUAL_RETRY_COOLDOWN_MS`), and how many a missed period gets. */\nconst FREE_RETRY_WAIT_MS = 10 * 60 * 1000;\nconst FREE_HAND_RETRIES = 3;\n/** Dev and Business: the wait after the first hand retry, doubling after each (`PAID_HAND_RETRY_WAIT_MS`). */\nconst PAID_RETRY_WAIT_MS = 20 * 60 * 1000;\n/** Two minutes before the end of the missed period, where Free's hand retries close (`retryClosesAt`). */\nconst CLOCK_MARGIN_MS = 2 * 60 * 1000;\n/** About 150 blocks of 200 ms: how long Mesub says a transaction it built lives. */\nconst TRANSACTION_LIFE_MS = 30_000;\nconst REASONS: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n 503: 'Service Unavailable',\n};\n\n/**\n * A fake Mesub API. Each test sets what a customer has with `grant`, `deny`\n * or `setAccess`, gets a client wired to it with `client()`, and makes it\n * fail with `fail()`. A plan with an end is `grantLastPeriod`, `endPlan`, or\n * an `ends_at` in `plans`.\n *\n * It answers and refuses as Mesub does, `revalidate_after` included: a\n * client keeps a yes up to five minutes and a no 10 seconds, so a change\n * made after a client asked shows to a new client, or to every client with\n * `revalidateAfter: 0`. Every answer carries a `Mesub-Request-Id`, and every\n * error body its `request_id`.\n *\n * `closable` and `closable_from` are worked out from what the fake's close\n * would take, unless a test set them on an answer. A subscription it moves\n * raises the event Mesub raises (`subscription.created` on a submit,\n * `cancelled`, `resumed` and `ended` on a confirm), which `/v1/events`\n * serves; `addEvent` records any other.\n */\nexport class FakeMesub {\n readonly baseUrl: string;\n readonly apiKey: string;\n readonly webhookSecret: string;\n /** Every call received, oldest first. */\n readonly requests: FakeRequest[] = [];\n /** Hand it to `new Mesub({ fetch })`, or use `client()`. */\n readonly fetch: typeof globalThis.fetch;\n\n readonly #plans: Set<string> | null;\n readonly #planList: Plan[];\n readonly #answers = new Map<string, Held>();\n /** The ends `grantLastPeriod` and `endPlan` gave a plan, by slug: they win over `plans`. */\n readonly #ends = new Map<string, string>();\n #subscriptions: ServerSubscription[] = [];\n /** The subscriptions a cancel, resume or close transaction was built for, by id. */\n readonly #built = new Map<string, Set<FakeAction>>();\n /** The rows ended with their plan whose close landed: nothing of them is left on chain. */\n readonly #gone = new Set<string>();\n /** The attempts `setAttempts` gave each subscription, newest first, by id. */\n readonly #attempts = new Map<string, SubscriptionAttempt[]>();\n readonly #attemptsRoute: boolean;\n readonly #revalidateAfter: number | undefined;\n #attemptIds = 0;\n #eventIds = 0;\n /** The last `sequence` given to each subscription's events, by id. */\n readonly #sequences = new Map<string, number>();\n readonly #accessEvents: boolean;\n #requestIds = 0;\n /** Every event raised or added, as `/v1/events` serves it. */\n #events: MesubEvent[] = [];\n readonly #eventsSince: string;\n #failure: FakeFailure | null = null;\n #ids = 0;\n /** The made-up subscriptions of `webhook` and `addEvent`, apart: they take no `sub_fake_` id. */\n #madeUpIds = 0;\n #deliveryIds = 0;\n readonly #tier: FakeTier;\n /** The hand retries fired on each late subscription, and when the last one was, by id. */\n readonly #handRetries = new Map<string, { count: number; at: number }>();\n\n constructor(options: FakeMesubOptions = {}) {\n this.baseUrl = (options.baseUrl ?? 'https://api.mesub.test').replace(/\\/+$/, '');\n this.apiKey = options.apiKey ?? 'SUB_test_fake';\n const plans = options.plans?.map((plan) =>\n typeof plan === 'string' ? fakePlan(plan) : fakePlan(plan.slug, plan),\n );\n this.#plans = plans ? new Set(plans.map((plan) => plan.slug)) : null;\n this.#planList = (plans ?? []).sort((a, b) => a.slug.localeCompare(b.slug));\n this.webhookSecret = options.webhookSecret ?? FAKE_WEBHOOK_SECRET;\n this.#attemptsRoute = options.attemptsRoute ?? true;\n this.#revalidateAfter = options.revalidateAfter;\n this.#accessEvents = options.accessEvents ?? false;\n this.#tier = options.tier ?? 'dev';\n this.#eventsSince = new Date(\n options.eventsSince ?? Date.now() - EVENTS_WINDOW_MS,\n ).toISOString();\n this.fetch = (input, init) => this.#handle(input, init);\n }\n\n /**\n * A client wired to this fake: its key, base URL and fetch, and no retry\n * unless asked, so a failure shows at once. Any other option is passed on.\n */\n client(\n options: Omit<MesubOptions, 'apiKey' | 'baseUrl' | 'fetch' | 'webhookSecret'> = {},\n ): Mesub {\n return new Mesub({\n maxRetries: 0,\n ...options,\n apiKey: this.apiKey,\n baseUrl: this.baseUrl,\n fetch: this.fetch,\n webhookSecret: this.webhookSecret,\n });\n }\n\n /**\n * That customer has access to that plan: active and paid, unless `fields`\n * say otherwise. The customer is named as `access` names them, and only\n * that way answers: a wallet granted is not found by its external id.\n */\n grant(customer: Customer | string, plan: string, fields: FakeAccess = {}): AccessAnswer {\n return this.setAccess(customer, plan, {\n access: true,\n status: 'active',\n payment_status: 'paid',\n ...fields,\n });\n }\n\n /** That customer has no access to that plan: `status` says why, `none` by default. */\n deny(customer: Customer | string, plan: string, fields: FakeAccess = {}): AccessAnswer {\n return this.setAccess(customer, plan, { ...fields, access: false });\n }\n\n /**\n * That customer is in the last period of a plan that ends at `endsAt`:\n * active and paid, access until the end and no charge ahead, unless\n * `fields` say otherwise. The plan gets that `ends_at`, and from then on\n * the fake refuses everyone on it, as Mesub does, whatever `status` reads.\n */\n grantLastPeriod(\n customer: Customer | string,\n plan: string,\n endsAt: string | Date,\n fields: FakeAccess = {},\n ): AccessAnswer {\n const end = new Date(endsAt).toISOString();\n\n this.#ends.set(plan, end);\n return this.grant(customer, plan, { access_until: end, ...fields });\n }\n\n /**\n * That plan reached its end, at `at` or now: it takes nobody new, and\n * what the fake holds on it is ended as Mesub ends it, `plan_ended` for a\n * running or late one, `cancelled` for one that had cancelled. Before\n * this is called, a plan past its `ends_at` already refuses everyone,\n * with `status` unchanged: the minutes Mesub takes to end the rows.\n */\n endPlan(plan: string, at: string | Date = new Date()): void {\n this.#ends.set(plan, new Date(at).toISOString());\n\n for (const [key, answer] of this.#answers) {\n if (answer.plan === plan) this.#answers.set(key, { ...answer, ...endedBy(answer) });\n }\n for (const subscription of this.#subscriptions) {\n if (subscription.plan !== plan) continue;\n const ended = endedBy(subscription);\n Object.assign(subscription, ended, 'status' in ended && NO_RETRY);\n }\n }\n\n /**\n * Parks that subscription's seat, as Mesub does when a project holds more\n * subscribers than its tier allows: `paused`, no next charge, access and\n * status unchanged (the fake moves no clock, so the paid period never\n * runs out). With `accessEvents`, raises `subscription.paused`\n * (Mesub-io/backend#509), sent behind the same flag. Throws for an id the\n * fake does not hold.\n */\n park(id: string): ServerSubscription {\n return this.#seat(id, true);\n }\n\n /**\n * Brings a parked seat back: not `paused`, charged again at the end of\n * its period. With `accessEvents`, raises `subscription.unpaused`.\n */\n unpark(id: string): ServerSubscription {\n return this.#seat(id, false);\n }\n\n #seat(id: string, paused: boolean): ServerSubscription {\n const subscription = this.#subscriptions.find((each) => each.id === id);\n if (!subscription) throw new Error(`The fake holds no subscription ${id}.`);\n if (subscription.paused === paused) return this.#view(subscription);\n\n const before = this.#view(subscription).access;\n const next_charge_at = paused ? null : subscription.current_period_end;\n Object.assign(subscription, { paused, next_charge_at });\n this.#answer(subscription, { paused, next_charge_at });\n if (this.#accessEvents) {\n this.#raise(\n paused ? 'subscription.paused' : 'subscription.unpaused',\n subscription,\n before,\n );\n }\n return this.#view(subscription);\n }\n\n /**\n * What `/v1/access` answers that customer on that plan, on top of the\n * answer for a customer with nothing. `wallet` defaults to the wallet\n * asked about, and to null for an external id or an email.\n */\n setAccess(customer: Customer | string, plan: string, fields: FakeAccess): AccessAnswer {\n const asked = customerOf(customer);\n const answer: Held = { ...nothing(asked, plan), ...fields, plan };\n\n this.#answers.set(keyOf(asked, plan), answer);\n return this.#fresh(answer);\n }\n\n /**\n * A subscription `retrieve` and `list` answer, active by default, dated\n * as Mesub dates a row of that `status` unless `fields` say otherwise:\n * active, paid for a period ending in 30 days and charged then; unpaid,\n * that period over, late, with access and a retry tomorrow, or on `free`\n * no access and hand retries until `retry_deadline`; cancelled, access to\n * the end of the period; stopped (`out_of_retries`) or ended (`closed`),\n * no access. Its access answer is left as it is: set it with `grant`.\n * Throws for a wallet Mesub would refuse. Its plan may be one `plans`\n * leaves out: Mesub serves the rows of a plan it no longer has.\n */\n addSubscription(\n fields: Partial<ServerSubscription> & { wallet: string; plan: string },\n ): ServerSubscription {\n if (!BASE58_ADDRESS.test(fields.wallet)) {\n throw new Error(`${fields.wallet} is not a wallet: a base58 Solana address.`);\n }\n const subscription = this.#subscription(fields);\n\n this.#subscriptions.push(subscription);\n return subscription;\n }\n\n /**\n * The pull attempts of that subscription, every one it ever had: what\n * `subscriptions.attempts` answers for it, with their `paid` total, and\n * the five newest through `/v1/access` with `attempts`, whose answer\n * becomes about that subscription. Each is a paid one of 9.99 USDC now,\n * priced 9.99 dollars, not a retry, unless told otherwise; one given\n * another `amount` and no `amount_usd` is not priced. Newest first.\n * Throws for an id the fake does not hold.\n */\n setAttempts(id: string, attempts: Array<Partial<SubscriptionAttempt>>): SubscriptionAttempt[] {\n const subscription = this.#subscriptions.find((each) => each.id === id);\n if (!subscription) throw new Error(`The fake holds no subscription ${id}.`);\n\n const now = new Date().toISOString();\n const filled = attempts\n .map((attempt): SubscriptionAttempt => ({\n id: `att_fake_${(this.#attemptIds += 1)}`,\n attempted_at: now,\n outcome: 'paid',\n reason: null,\n amount: '9990000',\n // The default amount's worth only: no price is guessed for another.\n amount_usd: attempt.amount === undefined ? '9.99' : null,\n // A mint that withholds nothing: the receiver got it all.\n amount_received: attempt.amount ?? '9990000',\n signature: 'fake_signature',\n retry: false,\n retry_number: null,\n retries_allowed: null,\n period_start: null,\n ...attempt,\n }))\n // Newest first, as Mesub serves them; as handed within one instant.\n .sort((a, b) => Date.parse(b.attempted_at) - Date.parse(a.attempted_at));\n\n this.#attempts.set(id, filled);\n this.#answer(subscription, {\n subscribed_since: subscription.confirmed_at,\n attempts: filled\n .slice(0, ACCESS_ATTEMPTS)\n .map(({ outcome, reason, amount, attempted_at, signature }): ServedAttempt => ({\n outcome,\n reason,\n amount,\n attempted_at,\n signature,\n })),\n });\n return filled;\n }\n\n /**\n * A webhook of that type, as Mesub posts it to an endpoint: the event's\n * body (Mesub-io/backend#482), on a made-up subscription and detail\n * unless `fields` say otherwise, the subscription the fake holds under\n * `fields.subscription.id` when it holds one, signed with\n * `webhookSecret`. The `test` event is Mesub's test delivery: `test:\n * true`, `evt_test`, on its sample subscription (`sub_test`,\n * `user_123`). Post it to your own handler, or hand it to\n * `webhooks.verify` of a `client()`. Nothing is added to the fake.\n */\n async webhook(type: WebhookEventType, fields: FakeWebhookFields = {}): Promise<SignedWebhook> {\n const now = new Date();\n const test = type === 'test';\n // A test delivery carries Mesub's sample subscription, the same every time.\n const data = test\n ? { ...TEST_SUBSCRIPTION, ...fields.subscription }\n : withClosing(this.#madeUp(type, fields.subscription));\n const sequence =\n fields.sequence === undefined\n ? test\n ? 1\n : this.#nextSequence(data.id)\n : fields.sequence;\n const payload = {\n id: fields.id ?? (test ? TEST_EVENT_ID : this.#nextEventId()),\n object: 'event',\n type,\n created_at: fields.created_at ?? now.toISOString(),\n network: 'mainnet-beta',\n api_version: API_VERSION,\n ...(sequence !== null && { sequence }),\n data,\n detail: fields.detail ?? detailOf(type, now, data),\n ...(test && { test: true }),\n };\n\n return signWebhook(payload, {\n secret: this.webhookSecret,\n id: fields.delivery_id ?? `msg_fake_${(this.#deliveryIds += 1)}`,\n ...(fields.timestamp !== undefined && { timestamp: fields.timestamp }),\n });\n }\n\n /**\n * The subscription an event carries: the one the fake holds under\n * `fields.id` when it holds one, else a made-up one, active and paid on\n * `pro` unless the type says otherwise; `fields` on top.\n */\n #madeUp(type: WebhookEventType, fields: Partial<ServerSubscription> = {}): ServerSubscription {\n const held = this.#subscriptions.find((each) => each.id === fields.id);\n const base = held\n ? this.#view(held)\n : {\n id: fields.id ?? `sub_fake_event_${(this.#madeUpIds += 1)}`,\n wallet: FAKE_WALLET,\n plan: 'pro',\n ...subscriptionOf(type),\n };\n\n return this.#subscription({ ...base, ...fields });\n }\n\n /**\n * An event `/v1/events` serves from now on, as Mesub would have raised\n * it: a made-up subscription and detail unless `fields` say otherwise,\n * the subscription the fake holds under `fields.subscription.id` when it\n * holds one. Nothing else changes: what it says happened is not done.\n */\n addEvent(type: EventType, fields: FakeEventFields = {}): MesubEvent {\n const now = new Date();\n const data = withClosing(this.#madeUp(type, fields.subscription));\n const sequence =\n fields.sequence === undefined ? this.#nextSequence(data.id) : fields.sequence;\n const event: MesubEvent = {\n id: fields.id ?? this.#nextEventId(),\n object: 'event',\n type,\n created_at: fields.created_at ?? now.toISOString(),\n network: 'mainnet-beta',\n api_version: API_VERSION,\n ...(sequence !== null && { sequence }),\n data,\n detail: (fields.detail ?? detailOf(type, now, data)) as never,\n } as MesubEvent;\n\n this.#events.push(event);\n return structuredClone(event);\n }\n\n /**\n * Every call answers that error from now on, `'outage'` a 503, until\n * `fail(null)`.\n */\n fail(failure: FakeFailure | 'outage' | null): void {\n this.#failure = failure === 'outage' ? OUTAGE : failure;\n }\n\n /**\n * Forgets every answer, subscription, failure and request, and starts\n * every id over: the same test makes the same ids, whatever ran before.\n */\n reset(): void {\n this.#answers.clear();\n this.#ends.clear();\n this.#subscriptions = [];\n this.#built.clear();\n this.#gone.clear();\n this.#attempts.clear();\n this.#events = [];\n this.#sequences.clear();\n this.#handRetries.clear();\n this.#failure = null;\n this.requests.length = 0;\n this.#ids = 0;\n this.#madeUpIds = 0;\n this.#eventIds = 0;\n this.#attemptIds = 0;\n this.#requestIds = 0;\n this.#deliveryIds = 0;\n }\n\n /** A subscription with every field, dated as Mesub dates one of its `status`. */\n #subscription(\n fields: Partial<ServerSubscription> & Pick<ServerSubscription, 'wallet' | 'plan'>,\n ): ServerSubscription {\n const now = Date.now();\n const iso = (ms: number) => new Date(ms).toISOString();\n\n return {\n id: fields.id ?? this.#nextId(),\n status: 'active',\n paused: false,\n end_reason: null,\n late_reason: null,\n stop_reason: null,\n blocked_since: null,\n blocked_reason: null,\n email: null,\n external_id: null,\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n closable: false,\n closable_from: null,\n created_at: iso(now),\n confirmed_at: iso(now),\n ...datesOf(fields.status ?? 'active', this.#tier, now),\n ...fields,\n };\n }\n\n /**\n * An answer as `/v1/access` serves it now: bounded by its plan's end, and\n * fresh for as long as Mesub keeps it, unless a test said otherwise.\n */\n #fresh(answer: Held): AccessAnswer {\n const end = this.#endOf(answer.plan);\n const served = bounded(answer, end);\n const closes = closingOf(served);\n const closed = {\n ...served,\n closable: served.closable ?? closes.closable,\n closable_from:\n served.closable_from === undefined ? closes.closable_from : served.closable_from,\n };\n\n return {\n ...closed,\n revalidate_after:\n served.revalidate_after ?? this.#revalidateAfter ?? revalidateAfter(closed, end),\n };\n }\n\n /**\n * A subscription as Mesub serves it now: bounded by its plan's end, and\n * `closable` as the fake's close would take it.\n */\n #view(subscription: ServerSubscription): ServerSubscription {\n const served = bounded(subscription, this.#endOf(subscription.plan));\n\n return { ...served, ...closingOf(served, this.#gone.has(subscription.id)) };\n }\n\n /**\n * Records the event Mesub raises for a change to that subscription, as\n * it stands now, then, with `accessEvents`, the access event of the same\n * change when access flipped from `before`: the same `data`, one\n * `sequence` later.\n */\n #raise(\n type: EventType,\n subscription: ServerSubscription,\n before: boolean,\n detail: object = {},\n ): void {\n const created_at = new Date().toISOString();\n const data = this.#view(subscription);\n const push = (each: EventType, eachDetail: object) =>\n this.#events.push({\n id: this.#nextEventId(),\n object: 'event',\n type: each,\n created_at,\n network: 'mainnet-beta',\n api_version: API_VERSION,\n sequence: this.#nextSequence(subscription.id),\n data: structuredClone(data),\n detail: eachDetail,\n } as MesubEvent);\n\n push(type, detail);\n if (this.#accessEvents && data.access !== before) {\n const flip = data.access\n ? 'subscription.access_granted'\n : 'subscription.access_revoked';\n push(flip, accessDetailOf(data, created_at));\n }\n }\n\n /** The next `sequence` of that subscription's events: 1, 2, 3... */\n #nextSequence(id: string): number {\n const next = (this.#sequences.get(id) ?? 0) + 1;\n this.#sequences.set(id, next);\n return next;\n }\n\n /** A plan's end: the one a test gave it, else its `ends_at` in `plans`. */\n #endOf(slug: string | null): string | null {\n if (slug === null) return null;\n\n return (\n this.#ends.get(slug) ??\n this.#planList.find((each) => each.slug === slug)?.ends_at ??\n null\n );\n }\n\n /** Whether that plan is past its end, where it takes nobody new. */\n #over(slug: string | null): boolean {\n const end = this.#endOf(slug);\n\n return end !== null && Date.parse(end) <= Date.now();\n }\n\n /** A plan as served: with its end, and taking nobody new once past it. */\n #served(plan: Plan): Plan {\n const ends_at = this.#endOf(plan.slug);\n\n return { ...plan, ends_at, available: plan.available && !this.#over(plan.slug) };\n }\n\n #nextId(): string {\n this.#ids += 1;\n return `sub_fake_${this.#ids}`;\n }\n\n #nextEventId(): string {\n this.#eventIds += 1;\n return `evt_fake_${this.#eventIds}`;\n }\n\n async #handle(input: string | URL | Request, init: RequestInit = {}): Promise<Response> {\n const url = new URL(input instanceof Request ? input.url : String(input));\n const base = new URL(this.baseUrl);\n const prefix = base.pathname.replace(/\\/+$/, '');\n\n // Anything else is not this fake: a 404 that is not Mesub's, as a wrong baseUrl gets.\n if (url.origin !== base.origin || !url.pathname.startsWith(`${prefix}/`)) {\n return new Response('Not Found', { status: 404 });\n }\n\n this.#requestIds += 1;\n const id = `req_fake${String(this.#requestIds).padStart(20, '0')}`;\n\n return stamped(await this.#route(url, url.pathname.slice(prefix.length), init), id);\n }\n\n async #route(url: URL, path: string, init: RequestInit): Promise<Response> {\n const method = (init.method ?? 'GET').toUpperCase();\n const headers = new Headers(init.headers);\n const query = Object.fromEntries(url.searchParams);\n const body = typeof init.body === 'string' ? (JSON.parse(init.body) as unknown) : undefined;\n this.requests.push({ method, path, query, headers, body });\n\n // As Mesub's ApiKeyGuard, which tells a missing key from a wrong one.\n const [scheme, key] = (headers.get('authorization') ?? '').split(' ');\n if (scheme?.toLowerCase() !== 'bearer' || !key) {\n return error(401, 'missing_api_key', 'Send your API key as a bearer token.');\n }\n if (key !== this.apiKey) {\n return error(401, 'invalid_api_key', 'That API key is not valid.');\n }\n // After the key, as Mesub's ApiVersionInterceptor: absent is the first\n // version; anything not, to the letter, a version served is refused.\n const version = headers.get(API_VERSION_HEADER);\n if (version !== null && version !== API_VERSION) {\n return error(\n 400,\n 'unsupported_version',\n `${API_VERSION_HEADER} must be one of the versions this API serves: ${API_VERSION}.`,\n );\n }\n if (this.#failure) {\n const { status, code, message, retryable } = this.#failure;\n const retryAfter =\n this.#failure.retryAfter ?? waitOf(method, path, status, code, this.#tier);\n return error(status, code, message ?? `Mesub answered ${status}.`, {\n ...(retryable !== undefined && { retryable }),\n ...(retryAfter !== undefined && { retryAfter }),\n });\n }\n\n if (method === 'GET' && path === '/v1/plans') {\n return Response.json({ plans: this.#planList.map((plan) => this.#served(plan)) });\n }\n const slug = /^\\/v1\\/plans\\/([^/]+)$/.exec(path)?.[1];\n if (method === 'GET' && slug !== undefined) {\n const plan =\n this.#planList.find((each) => each.slug === slug) ??\n // No list given: every slug exists, as for access.\n (this.#plans === null ? fakePlan(slug) : undefined);\n\n return plan\n ? Response.json(this.#served(plan))\n : error(404, 'plan_not_found', `No plan under slug ${slug}`);\n }\n\n if (method === 'GET' && path === '/v1/access') return this.#access(query);\n if (method === 'GET' && path === '/v1/events') return this.#eventList(query);\n const eventId = /^\\/v1\\/events\\/([^/]+)$/.exec(path)?.[1];\n if (method === 'GET' && eventId !== undefined) {\n const event = this.#servedEvents().find(\n (each) => each.id === decodeURIComponent(eventId),\n );\n\n return event\n ? Response.json(event)\n : error(\n 404,\n 'event_not_found',\n 'No event of yours under that id in the last 30 days.',\n );\n }\n if (method === 'GET' && path === '/v1/subscriptions') return this.#list(query);\n if (method === 'POST' && path === '/v1/subscriptions') return this.#create(body);\n\n const [, id, step, confirm] =\n /^\\/v1\\/subscriptions\\/([^/]+)(?:\\/(submit|transaction|cancel|resume|close|attempts|retry)(\\/confirm)?)?$/.exec(\n path,\n ) ?? [];\n // A Mesub without the route knows no such path, whatever the id.\n if (step === 'attempts' && (confirm || !this.#attemptsRoute)) {\n return error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n // Mesub's DTO, before any lookup.\n if (method === 'POST' && step === 'submit' && !confirm) {\n const invalid = invalidSubmit(body);\n if (invalid) return invalid;\n }\n if (id !== undefined) {\n const subscription = this.#subscriptions.find((s) => s.id === decodeURIComponent(id));\n if (!subscription) {\n return error(404, 'subscription_not_found', 'No subscription under that id.');\n }\n if (method === 'GET' && !step) return Response.json(this.#view(subscription));\n if (step === 'attempts') {\n return method === 'GET'\n ? this.#attemptsOf(subscription, query)\n : error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n if (method === 'POST' && step === 'submit' && !confirm) {\n return this.#submit(subscription);\n }\n // A fresh transaction (Mesub-io/backend#427): for a pending one only.\n if (step === 'transaction') {\n return method === 'POST' && !confirm\n ? this.#transaction(subscription)\n : error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n // Pay now (Mesub-io/backend#354): taken for a late one, nothing pulled here.\n if (step === 'retry') {\n return method === 'POST' && !confirm\n ? this.#retry(subscription)\n : error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n if (method === 'POST' && step && step !== 'submit') {\n return confirm\n ? this.#confirm(step as FakeAction, subscription, body)\n : this.#build(step as FakeAction, subscription);\n }\n }\n\n return error(404, 'not_found', `Cannot ${method} ${path}`);\n }\n\n #access(query: Record<string, string>): Response {\n const invalid = invalidContact(query);\n if (invalid) return invalid;\n const asked = askedIn(query);\n if (!asked)\n return error(400, 'invalid_request', 'Name one of wallet, external_id or email.');\n\n const { plan } = query;\n const attempts = query['attempts'] === 'true';\n\n if (plan === undefined) {\n const plans = [...this.#answers.entries()]\n .filter(([key]) => key.startsWith(keyOf(asked, '')))\n .map(([, answer]) => withAttempts(this.#fresh(answer), attempts));\n // As Mesub: stale with the first yes that changes; a no would hold\n // the whole list to 10, so with no yes it is the list's own 10.\n const granted = plans.filter((answer) => answer.access);\n\n return Response.json({\n plans,\n revalidate_after: granted.length\n ? Math.min(...granted.map((answer) => answer.revalidate_after))\n : (this.#revalidateAfter ?? REVALIDATE_MIN_S),\n });\n }\n if (this.#plans && !this.#plans.has(plan)) {\n return error(404, 'plan_not_found', `No plan under slug ${plan}`);\n }\n\n const answer = this.#answers.get(keyOf(asked, plan)) ?? nothing(asked, plan);\n return Response.json(withAttempts(this.#fresh(answer), attempts));\n }\n\n #list(query: Record<string, string>): Response {\n const invalid = invalidContact(query);\n if (invalid) return invalid;\n const asked = askedIn(query);\n if (!asked)\n return error(400, 'invalid_request', 'Name one of wallet, external_id or email.');\n\n const limit = query['limit'] === undefined ? 20 : limitOf(query['limit']);\n if (limit === null) return error(400, 'invalid_request', [LIMIT_MESSAGE]);\n // Newest first. The cursor is a position among every subscription\n // held, whoever it is for, and one not held is a 400, as on Mesub.\n const all = [...this.#subscriptions].reverse();\n const after = query['starting_after'];\n const from = after === undefined ? 0 : all.findIndex((s) => s.id === after) + 1;\n if (after !== undefined && from === 0) {\n return error(400, 'invalid_request', 'starting_after is not a subscription of yours.');\n }\n const data = all\n .slice(from)\n // The comeback that replaced it stands for the customer (Mesub-io/backend#254).\n .filter((s) => s.status !== 'superseded')\n .filter((s) => s[asked.kind] === asked.value)\n .filter((s) => query['plan'] === undefined || s.plan === query['plan']);\n\n const page: ServerSubscriptionList = {\n data: data.slice(0, limit).map((s) => this.#view(s)),\n has_more: data.length > limit,\n };\n return Response.json(page);\n }\n\n /** The events Mesub serves now: within the window and from `available_since`, newest first. */\n #servedEvents(): MesubEvent[] {\n const floor = Math.max(Date.now() - EVENTS_WINDOW_MS, Date.parse(this.#eventsSince));\n\n return this.#events\n .filter((event) => Date.parse(event.created_at) >= floor)\n .sort(\n (a, b) =>\n Date.parse(b.created_at) - Date.parse(a.created_at) ||\n // Within a millisecond, by `sequence`, highest first, as Mesub.\n (b.sequence ?? 0) - (a.sequence ?? 0) ||\n (a.id < b.id ? 1 : a.id > b.id ? -1 : 0),\n );\n }\n\n #eventList(query: Record<string, string>): Response {\n const { type, subscription, starting_after: after } = query;\n const limit = Number(query['limit'] ?? EVENTS_LIMIT);\n const reasons = [\n !Number.isInteger(limit) || limit < 1 || limit > EVENTS_LIMIT_MAX\n ? `limit must be a whole number from 1 to ${EVENTS_LIMIT_MAX}`\n : null,\n type !== undefined && !EVENT_TYPES.includes(type as EventType)\n ? `type is one of ${EVENT_TYPES.join(', ')}`\n : null,\n subscription !== undefined && !RESOURCE_ID.test(subscription)\n ? 'subscription must be a subscription id'\n : null,\n after !== undefined && !RESOURCE_ID.test(after)\n ? 'starting_after must be an event id'\n : null,\n ].filter((reason): reason is string => reason !== null);\n if (reasons.length) return error(400, 'invalid_request', reasons);\n\n // The cursor is a position among every event served, whatever the filters.\n const all = this.#servedEvents();\n const from = after === undefined ? 0 : all.findIndex((each) => each.id === after) + 1;\n if (after !== undefined && from === 0) {\n return error(400, 'invalid_request', 'starting_after is not an event of yours.');\n }\n const data = all\n .slice(from)\n .filter((each) => type === undefined || each.type === type)\n .filter((each) => subscription === undefined || each.data.id === subscription);\n\n const page: EventList = {\n data: data.slice(0, limit),\n has_more: data.length > limit,\n available_since: this.#eventsSince,\n };\n return Response.json(page);\n }\n\n #attemptsOf(subscription: ServerSubscription, query: Record<string, string>): Response {\n const all = this.#attempts.get(subscription.id) ?? [];\n const limit = Number(query['limit'] ?? 20);\n const after = query['starting_after'];\n const from = after === undefined ? 0 : all.findIndex((each) => each.id === after) + 1;\n\n if (!Number.isInteger(limit) || limit < 1 || limit > 100) {\n return error(400, 'invalid_request', 'limit must be a whole number from 1 to 100');\n }\n if (after !== undefined && from === 0) {\n return error(\n 400,\n 'invalid_request',\n 'starting_after is not an attempt of this subscription.',\n );\n }\n\n const paid = all.filter((each) => each.outcome === 'paid');\n const page: SubscriptionAttemptList = {\n data: all.slice(from, from + limit),\n has_more: all.length > from + limit,\n // Over every attempt, not the page; an amount that is no whole number counts for 0.\n paid: {\n count: paid.length,\n amount: paid\n .reduce(\n (sum, each) => sum + (/^\\d+$/.test(each.amount) ? BigInt(each.amount) : 0n),\n 0n,\n )\n .toString(),\n },\n };\n return Response.json(page);\n }\n\n #create(body: unknown): Response {\n const { plan, wallet, email, external_id } = (body ?? {}) as Record<string, unknown>;\n\n if (typeof plan !== 'string' || typeof wallet !== 'string') {\n return error(400, 'invalid_request', 'plan and wallet are required.');\n }\n const invalid = invalidContact({ wallet, email });\n if (invalid) return invalid;\n if (this.#plans && !this.#plans.has(plan)) {\n return error(404, 'plan_not_found', `No plan under slug ${plan}`);\n }\n\n if (this.#over(plan)) return error(409, 'plan_ended', 'This plan has ended.');\n\n const contact = {\n email: typeof email === 'string' ? email.trim().toLowerCase() : null,\n external_id: typeof external_id === 'string' ? external_id.trim() : null,\n };\n const held = this.#subscriptions.filter((s) => s.plan === plan && s.wallet === wallet);\n const refusal = liveRefusal(held);\n if (refusal) return refusal;\n\n // One checkout per customer on a wallet, as Mesub keeps them: the same\n // customer gets its own back, another one never does.\n const waiting = held.find(\n (s) => s.status === 'pending' && holderOf(s) === holderOf(contact),\n );\n if (waiting && contact.email !== null) waiting.email = contact.email;\n\n const subscription =\n waiting ??\n this.addSubscription({\n plan,\n wallet,\n ...contact,\n status: 'pending',\n access: false,\n payment_status: 'none',\n current_period_start: null,\n confirmed_at: null,\n });\n\n return Response.json(handedOut(subscription), { status: 201 });\n }\n\n /**\n * A fresh transaction for a subscription still pending, with the same\n * terms message as `create` gave it: a signature held stays good. Refused\n * as Mesub refuses it otherwise.\n */\n #transaction(subscription: ServerSubscription): Response {\n if (subscription.status !== 'pending') {\n return error(409, 'not_awaiting_signature', 'That subscription awaits no signature.');\n }\n if (this.#over(subscription.plan)) return error(409, 'plan_ended', 'This plan has ended.');\n\n const refusal = liveRefusal(\n this.#subscriptions.filter(\n (s) => s.plan === subscription.plan && s.wallet === subscription.wallet,\n ),\n );\n if (refusal) return refusal;\n\n return Response.json(handedOut(subscription), { status: 201 });\n }\n\n /**\n * Lands at once: the subscription turns active and its customer is granted\n * the plan. The other checkouts waiting on that wallet expire, as on Mesub.\n */\n #submit(subscription: ServerSubscription): Response {\n if (subscription.status !== 'pending') {\n return error(409, 'not_awaiting_signature', 'That subscription awaits no signature.');\n }\n // Read again at the submit: a plan that ended since the create takes nobody.\n if (this.#over(subscription.plan)) return error(409, 'plan_ended', 'This plan has ended.');\n for (const other of this.#subscriptions) {\n const beside = other.plan === subscription.plan && other.wallet === subscription.wallet;\n if (beside && other !== subscription && other.status === 'pending') {\n other.status = 'expired';\n }\n }\n\n const now = new Date();\n const end = new Date(now.getTime() + PERIOD_MS).toISOString();\n // A plan that ends inside the period: access to its end, and no second charge.\n const dates = bounded(\n { access: true, access_until: end, next_charge_at: end, next_retry_at: null },\n this.#endOf(subscription.plan),\n );\n Object.assign(subscription, {\n status: 'active',\n payment_status: 'paid',\n current_period_start: now.toISOString(),\n current_period_end: end,\n confirmed_at: now.toISOString(),\n ...dates,\n } satisfies Partial<ServerSubscription>);\n\n const plan = subscription.plan ?? '';\n const fields: FakeAccess = {\n wallet: subscription.wallet,\n subscription_id: subscription.id,\n subscribed_since: now.toISOString(),\n first_subscribed_at: now.toISOString(),\n current_period_end: end,\n access_until: dates.access_until,\n next_charge_at: dates.next_charge_at,\n };\n this.grant({ wallet: subscription.wallet }, plan, fields);\n if (subscription.external_id) {\n this.grant({ external_id: subscription.external_id }, plan, fields);\n }\n if (subscription.email) this.grant({ email: subscription.email }, plan, fields);\n // Pending, it granted nothing. What it paid, as Mesub-io/backend#509 tells it.\n const terms = this.#planList.find((each) => each.slug === subscription.plan);\n this.#raise('subscription.created', subscription, false, {\n first_payment: {\n amount: terms?.amount ?? '9990000',\n mint: terms?.mint ?? USDC,\n signature: 'fake_signature',\n },\n });\n\n return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n }\n\n /**\n * A hand retry, refused as Mesub's `RetryNowService` refuses it from what\n * the fake holds, in its order: a parked late seat, a row not late, a\n * plan past its end, the wait since the last one (by `tier`, with\n * `Retry-After`), and on Free a `retry_deadline` passed or the three\n * retries spent. Taken, it pulls nothing and the row stays `unpaid`: the\n * fake counts it as one more that failed. What only the chain or a race\n * decides (`insufficient_balance`, `retry_cannot_succeed`,\n * `pull_running`, `subscription_changed`) is answered with `fail()`.\n */\n #retry(subscription: ServerSubscription): Response {\n if (subscription.status === 'unpaid' && subscription.paused) {\n return error(\n 409,\n 'subscription_paused',\n \"This subscription is paused over the merchant's tier limit, so it cannot be retried.\",\n );\n }\n if (subscription.status !== 'unpaid') {\n return error(\n 409,\n 'subscription_not_late',\n 'This subscription is not behind on its payment.',\n );\n }\n if (this.#over(subscription.plan)) {\n return error(\n 409,\n 'plan_ended',\n 'This plan has ended, so this payment can no longer be collected.',\n );\n }\n\n const now = Date.now();\n const free = this.#tier === 'free';\n const fired = this.#handRetries.get(subscription.id) ?? { count: 0, at: 0 };\n const openAt =\n fired.count === 0\n ? 0\n : fired.at +\n (free ? FREE_RETRY_WAIT_MS : PAID_RETRY_WAIT_MS * 2 ** (fired.count - 1));\n if (openAt > now) {\n const minutes = Math.ceil((openAt - now) / 60_000);\n return error(\n 409,\n 'retry_too_soon',\n `Try again in ${minutes} ${minutes === 1 ? 'minute' : 'minutes'}.`,\n { retryAfter: Math.ceil((openAt - now) / 1000) },\n );\n }\n if (free) {\n const deadline = subscription.retry_deadline;\n if (deadline !== null && Date.parse(deadline) <= now) {\n return error(\n 409,\n 'retry_deadline_passed',\n 'The missed period ends too soon to retry.',\n );\n }\n if (fired.count >= FREE_HAND_RETRIES) {\n return error(409, 'retries_spent', 'This subscription has no retries left.');\n }\n }\n\n this.#handRetries.set(subscription.id, { count: fired.count + 1, at: now });\n return Response.json(this.#view(subscription), { status: 202 });\n }\n\n /** The transaction of a cancel, resume or close, or Mesub's refusal of it. */\n #build(action: FakeAction, subscription: ServerSubscription): Response {\n const refusal = refusalOf(action, subscription, this.#gone.has(subscription.id));\n if (refusal) return refusal;\n\n const built = this.#built.get(subscription.id) ?? new Set();\n this.#built.set(subscription.id, built.add(action));\n\n const answer: WalletTransaction = {\n transaction: btoa(`fake ${action} transaction`),\n last_valid_block_height: '0',\n transaction_expires_at: expiresAt(),\n };\n return Response.json(answer, { status: 201 });\n }\n\n /** Lands at once: the subscription moves, and so do its customer's access answers. */\n #confirm(action: FakeAction, subscription: ServerSubscription, body: unknown): Response {\n const { signature } = (body ?? {}) as Record<string, unknown>;\n\n if (typeof signature !== 'string' || signature === '') {\n return error(400, 'invalid_request', 'signature must be a transaction signature.');\n }\n const gone = this.#gone.has(subscription.id);\n // One ended with its plan is ended before its close and after it (#344).\n const done =\n subscription.status === DONE[action] && !(endedWithPlan(subscription) && !gone);\n const refusal = done ? null : refusalOf(action, subscription, gone);\n if (refusal) return refusal;\n if (!this.#built.get(subscription.id)?.has(action)) {\n return error(400, 'nothing_to_confirm', 'There is no transaction to confirm yet.');\n }\n // Confirmed already: answered with the row, as Mesub does.\n if (done) return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n // What was built for another action is spent.\n this.#built.set(subscription.id, new Set([action]));\n const before = this.#view(subscription).access;\n\n const now = new Date().toISOString();\n const end = subscription.current_period_end;\n\n if (action === 'close' && endedWithPlan(subscription)) {\n // As Mesub's confirmClose: its account is gone, and the row stays\n // ended with the reason it ended for. Nothing ends twice.\n this.#gone.add(subscription.id);\n this.#answer(subscription, {});\n return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n }\n if (action === 'cancel') {\n // Paid up, it keeps its access to the end of the period.\n Object.assign(subscription, {\n status: 'cancelled',\n stop_reason: null,\n ...NO_BLOCK,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n access_until: subscription.access ? (subscription.access_until ?? end) : null,\n } satisfies Partial<ServerSubscription>);\n this.#answer(subscription, {\n status: 'cancelled',\n stop_reason: null,\n ...NO_BLOCK,\n // The access a cancellation keeps, also where no grant dated it.\n access_until: subscription.access_until,\n cancelled_at: now,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n });\n } else if (action === 'resume') {\n Object.assign(subscription, {\n status: 'active',\n next_charge_at: end,\n } satisfies Partial<ServerSubscription>);\n this.#answer(subscription, {\n status: 'active',\n cancelled_at: null,\n next_charge_at: end,\n });\n } else {\n Object.assign(subscription, {\n status: 'ended',\n end_reason: 'closed',\n stop_reason: null,\n ...NO_BLOCK,\n access: false,\n payment_status: 'none',\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n next_retry_number: null,\n retries_allowed: null,\n } satisfies Partial<ServerSubscription>);\n this.#answer(subscription, {\n access: false,\n status: 'ended',\n end_reason: 'closed',\n stop_reason: null,\n ...NO_BLOCK,\n payment_status: 'none',\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n });\n }\n this.#raise(EVENT_OF[action], subscription, before);\n\n return Response.json({ subscription: this.#view(subscription) }, { status: 201 });\n }\n\n /**\n * Changes what `/v1/access` answers a subscription's customer, by wallet,\n * external id and email, on top of what each was answered before.\n */\n #answer(subscription: ServerSubscription, fields: FakeAccess): void {\n const plan = subscription.plan ?? '';\n const customers: Customer[] = [{ wallet: subscription.wallet }];\n if (subscription.external_id) customers.push({ external_id: subscription.external_id });\n if (subscription.email) customers.push({ email: subscription.email });\n\n for (const customer of customers) {\n const before = this.#answers.get(keyOf(customerOf(customer), plan));\n\n this.setAccess(customer, plan, {\n wallet: subscription.wallet,\n access: subscription.access,\n payment_status: subscription.payment_status,\n ...before,\n subscription_id: subscription.id,\n // A row whose close landed has nothing left to close; any\n // other is worked out when served.\n ...(this.#gone.has(subscription.id) && { closable: false, closable_from: null }),\n ...fields,\n });\n }\n }\n}\n\n/**\n * How long Mesub keeps an answer fresh, as the back's `AccessService` works\n * it out: 10 s for a no, which a subscribe may lift any second; for a yes,\n * until the first moment it changes on its own (the next retry, else the\n * next charge, else a cancellation's or a parked period's end; the plan's\n * end; a blocked row's `access_until`), from 10 s to 300 s, and 300 with\n * none ahead.\n */\nfunction revalidateAfter(answer: Omit<AccessAnswer, 'revalidate_after'>, planEnd: string | null) {\n if (!answer.access) return REVALIDATE_MIN_S;\n\n const next = soonest(\n answer.next_retry_at ??\n answer.next_charge_at ??\n soonest(\n answer.paused ? answer.current_period_end : null,\n answer.status === 'cancelled' ? answer.access_until : null,\n ),\n planEnd,\n answer.blocked_since === null ? null : answer.access_until,\n );\n if (next === null) return REVALIDATE_MAX_S;\n\n const seconds = Math.floor((Date.parse(next) - Date.now()) / 1000);\n return Math.min(REVALIDATE_MAX_S, Math.max(REVALIDATE_MIN_S, seconds));\n}\n\n/** The earliest of these dates, null when none is given. */\nfunction soonest(...dates: Array<string | null>): string | null {\n return dates.reduce<string | null>(\n (first, date) =>\n date === null || (first !== null && Date.parse(first) <= Date.parse(date))\n ? first\n : date,\n null,\n );\n}\n\n/** What an access answer and a subscription both say of access and of the pulls ahead. */\ntype Dated = Pick<AccessAnswer, 'access' | 'access_until' | 'next_charge_at' | 'next_retry_at'>;\n\n/**\n * A row as Mesub serves it on a plan that ends at `end`: no access from the\n * end on, whatever its status, and before it no `access_until` later than\n * the end and no pull dated at or after it. The real margin (a pull due in\n * the two minutes before the end is not served either) is left out.\n */\nfunction bounded<T extends Dated>(row: T, end: string | null): T {\n if (end === null || !row.access) return row;\n\n const at = Date.parse(end);\n const none = { next_charge_at: null, next_retry_at: null };\n if (at <= Date.now()) return { ...row, ...none, access: false, access_until: null };\n\n const ahead = (date: string | null) => (date !== null && Date.parse(date) < at ? date : null);\n\n return {\n ...row,\n access_until:\n row.access_until !== null && Date.parse(row.access_until) > at ? end : row.access_until,\n next_charge_at: ahead(row.next_charge_at),\n next_retry_at: ahead(row.next_retry_at),\n };\n}\n\n/** What a row of that status reads as on Mesub, dated from `now`, ms. */\ntype StatusDates = Pick<\n ServerSubscription,\n | 'access'\n | 'payment_status'\n | 'current_period_start'\n | 'current_period_end'\n | 'next_charge_at'\n | 'access_until'\n> &\n Partial<ServerSubscription>;\n\n/**\n * The fields Mesub serves a row of that status with: a period that runs 30\n * days from now on a running or cancelled one, a period just over on a late,\n * stopped or ended one, and none on a checkout. Late, it keeps access to its\n * one retry, tomorrow, except on Free, which grants none to a late row and\n * dates the close of its hand retries instead (`billingDatesOf`).\n */\nfunction datesOf(status: ServerSubscription['status'], tier: FakeTier, now: number): StatusDates {\n const iso = (ms: number) => new Date(ms).toISOString();\n const running = { current_period_start: iso(now), current_period_end: iso(now + PERIOD_MS) };\n // The period that went unpaid, or the last one before the row stopped.\n const behind = { current_period_start: iso(now - PERIOD_MS), current_period_end: iso(now) };\n const nothing = {\n access: false,\n payment_status: 'none',\n next_charge_at: null,\n access_until: null,\n } as const;\n\n switch (status) {\n case 'active':\n return {\n ...running,\n access: true,\n payment_status: 'paid',\n next_charge_at: running.current_period_end,\n access_until: running.current_period_end,\n };\n case 'cancelled':\n return {\n ...running,\n access: true,\n payment_status: 'paid',\n next_charge_at: null,\n access_until: running.current_period_end,\n };\n case 'unpaid': {\n const late = {\n ...behind,\n late_reason: 'insufficient_balance',\n payment_status: 'late',\n next_charge_at: null,\n } as const;\n if (tier === 'free') {\n // The close of hand retries: two minutes before the missed period ends.\n const deadline = iso(now + PERIOD_MS - CLOCK_MARGIN_MS);\n return { ...late, access: false, access_until: null, retry_deadline: deadline };\n }\n const retry = iso(now + DAY_MS);\n return {\n ...late,\n access: true,\n // Its last retry: access stops there if that one fails too.\n access_until: retry,\n next_retry_at: retry,\n next_retry_number: 1,\n retries_allowed: 1,\n };\n }\n case 'stopped':\n return { ...behind, ...nothing, stop_reason: 'out_of_retries' };\n case 'ended':\n return { ...behind, ...nothing, end_reason: 'closed' };\n case 'superseded':\n return { ...behind, ...nothing };\n default:\n // pending, expired, failed: a checkout nothing landed for.\n return {\n ...nothing,\n current_period_start: null,\n current_period_end: null,\n confirmed_at: null,\n };\n }\n}\n\n/** About half a minute from now, as Mesub dates a transaction's `last_valid_block_height`. */\nfunction expiresAt(): string {\n return new Date(Date.now() + TRANSACTION_LIFE_MS).toISOString();\n}\n\n/** What `create` and `transaction` hand out for a pending subscription: nothing a chain would take. */\nfunction handedOut(subscription: ServerSubscription): SubscribeTransaction {\n const zero = '0';\n\n return {\n subscription: { id: subscription.id, status: 'pending' },\n transaction: btoa('fake transaction'),\n last_valid_block_height: zero,\n transaction_expires_at: expiresAt(),\n costs: {\n rent: { subscription: zero, authority: null, total: zero },\n fee: { signatures: 2, per_signature: zero, priority: zero, total: zero },\n total: zero,\n },\n terms: {\n message: `Fake Mesub terms: ${subscription.wallet} subscribes to ${subscription.plan}.`,\n expires_at: new Date(Date.now() + 5 * 60_000).toISOString(),\n },\n };\n}\n\n/** A block is only ever on a running row: gone once it is cancelled or over. */\nconst NO_BLOCK = { blocked_since: null, blocked_reason: null } as const;\n\n/** What only a subscription carries of a retry, gone with the retry. */\nconst NO_RETRY = { next_retry_number: null, retries_allowed: null } as const;\n\n/** What a plan's end makes of a row held on it; nothing of one that was over already. */\nfunction endedBy(row: {\n status: ServerSubscription['status'] | AccessAnswer['status'];\n}): Partial<Omit<AccessAnswer, 'status'>> & { status?: 'ended' } {\n const running = row.status === 'active' || row.status === 'unpaid';\n if (!running && row.status !== 'cancelled') return {};\n\n return {\n status: 'ended',\n end_reason: running ? 'plan_ended' : 'cancelled',\n late_reason: null,\n ...NO_BLOCK,\n paused: false,\n access: false,\n payment_status: 'none',\n access_until: null,\n next_charge_at: null,\n next_retry_at: null,\n retry_deadline: null,\n };\n}\n\n/** What stops, takes back or closes a subscription in the fake. */\ntype FakeAction = 'cancel' | 'resume' | 'close';\n\n/** The event each confirmed action raises. */\nconst EVENT_OF: Record<FakeAction, EventType> = {\n cancel: 'subscription.cancelled',\n resume: 'subscription.resumed',\n close: 'subscription.ended',\n};\n\n/** What decides a close, on a subscription and on an access answer alike. */\ntype Closing = Pick<ServerSubscription, 'access_until' | 'end_reason' | 'stop_reason'> & {\n status: ServerSubscription['status'] | AccessAnswer['status'];\n};\n\n/**\n * Whether the fake's close would take it now (`refusalOf`), and from when\n * when it would not yet: the end of a cancellation's period.\n */\nfunction closingOf(\n row: Closing,\n gone = false,\n): Pick<ServerSubscription, 'closable' | 'closable_from'> {\n const closable = refusalOf('close', row, gone) === null;\n const ahead = row.access_until !== null && Date.parse(row.access_until) > Date.now();\n\n return {\n closable,\n closable_from: !closable && row.status === 'cancelled' && ahead ? row.access_until : null,\n };\n}\n\n/** A subscription with `closable` as the fake's close would take it. */\nfunction withClosing(subscription: ServerSubscription): ServerSubscription {\n return { ...subscription, ...closingOf(subscription) };\n}\n\n/** The status each action leaves a subscription in. */\nconst DONE: Record<FakeAction, ServerSubscription['status']> = {\n cancel: 'cancelled',\n resume: 'active',\n close: 'ended',\n};\n\n/**\n * Mesub's 409 for an action a subscription's status does not allow, or null.\n * `gone`: a row ended with its plan whose close landed already.\n */\nfunction refusalOf(action: FakeAction, subscription: Closing, gone = false): Response | null {\n const { status, access_until } = subscription;\n const over = access_until === null || Date.parse(access_until) <= Date.now();\n const cancelled =\n status === 'cancelled' || (status === 'ended' && subscription.end_reason === 'cancelled');\n\n if (action === 'cancel') {\n if (status === 'cancelled') {\n return error(409, 'subscription_cancelled', 'This subscription is cancelled already.');\n }\n return ['active', 'unpaid', 'stopped'].includes(status)\n ? null\n : error(409, 'subscription_not_active', 'This subscription is not running.');\n }\n if (action === 'resume') {\n if (status !== 'cancelled') {\n return error(409, 'subscription_not_cancelled', 'This subscription is not cancelled.');\n }\n return over\n ? error(409, 'subscription_ended', 'The period paid for is over: subscribe again.')\n : null;\n }\n // A close, as Mesub's buildCloseTransaction answers it. Ended with its\n // plan, the delegation still holds the deposit (#344): it closes, once.\n if (endedWithPlan(subscription)) {\n return gone\n ? error(\n 409,\n 'subscription_not_on_chain',\n 'This subscription was closed outside Mesub, so it has ended.',\n )\n : null;\n }\n if (isClosed(subscription)) {\n return error(409, 'subscription_not_on_chain', 'This subscription is closed already.');\n }\n if (status === 'ended' && !cancelled) {\n return error(\n 409,\n 'subscription_not_on_chain',\n 'This subscription has ended and cannot be closed through Mesub.',\n );\n }\n // Its authority closed for good (Mesub-io/backend#394): closed at once,\n // nothing to cancel first.\n if (status === 'stopped' && subscription.stop_reason === 'authority_closed') return null;\n // Any other stopped one is one nothing cancelled, which Mesub cancels\n // first: a cancellation signed without Mesub is not modelled here.\n if (!cancelled) {\n return error(\n 409,\n 'subscription_not_cancelled',\n 'Cancel this subscription before closing it.',\n );\n }\n return over\n ? null\n : error(409, 'close_too_early', `This subscription runs until ${access_until}.`);\n}\n\n/**\n * Mesub's 409 for a checkout beside the subscription a wallet holds on the\n * plan, by its status, as the back's `liveRefusal` words it; null when it\n * holds none. A cancelled one past its end is held until it is closed: it\n * reads `ended`, with `end_reason` `cancelled`. A stopped one is not: the\n * fake's is one nothing cancelled, which the wallet comes back over\n * (Mesub-io/backend#211).\n */\nfunction liveRefusal(held: ServerSubscription[]): Response | null {\n const live = held.find(\n (s) =>\n ['active', 'cancelled', 'unpaid'].includes(s.status) ||\n (s.status === 'ended' && s.end_reason === 'cancelled'),\n );\n\n // Its authority closed for good: no comeback goes through it (Mesub-io/backend#394).\n const dead = held.find((s) => s.status === 'stopped' && s.stop_reason === 'authority_closed');\n if (dead && !live) {\n return error(\n 409,\n 'subscription_stopped',\n \"Your wallet's approval for this token was closed for good, so this subscription cannot restart. Close it, then subscribe again.\",\n );\n }\n\n if (!live) return null;\n if (live.status === 'active') {\n return error(409, 'already_subscribed', 'You are already subscribed to this plan.');\n }\n if (live.status === 'unpaid') {\n return error(\n 409,\n 'payment_pending',\n 'You already have a subscription to this plan, and its last payment is late. Open it in your subscriptions to see how to fix it, or cancel it.',\n );\n }\n const until = live.status === 'cancelled' ? live.access_until : null;\n return error(\n 409,\n 'subscription_cancelled',\n until !== null && Date.parse(until) > Date.now()\n ? `Your subscription to this plan is cancelled but runs until ${until}: resume it instead of subscribing again.`\n : 'Your cancelled subscription to this plan must be closed first: close it, then subscribe again.',\n );\n}\n\n/** Whose checkout it is, as Mesub tells them apart: the external id, else the email, else nobody. */\nfunction holderOf(contact: Pick<ServerSubscription, 'email' | 'external_id'>): string {\n if (contact.external_id) return `x:${contact.external_id}`;\n\n return contact.email ? `e:${contact.email}` : '-';\n}\n\n/** The reasons a row ends with its plan: removed, replaced, or past its end. */\nconst WITH_ITS_PLAN: Array<ServerSubscription['end_reason']> = [\n 'plan_removed',\n 'plan_replaced',\n 'plan_ended',\n];\n\n/** A row ended with its plan, which Mesub still closes (Mesub-io/backend#344). */\nfunction endedWithPlan(subscription: Closing): boolean {\n return subscription.status === 'ended' && WITH_ITS_PLAN.includes(subscription.end_reason);\n}\n\nfunction isClosed(subscription: Closing): boolean {\n return subscription.status === 'ended' && subscription.end_reason === 'closed';\n}\n\nfunction keyOf(asked: Asked, plan: string): string {\n return `${asked.kind}:${asked.value}\\n${plan}`;\n}\n\n/** Only `signWebhook`'s own: a fake's deliveries are numbered by the fake, apart. */\nlet signedIds = 0;\n\n/**\n * Signs a body as Mesub does (Standard Webhooks, HMAC-SHA256 of\n * `id.timestamp.body`), for a webhook of your own making: an object is sent\n * as its JSON, a string as is. `FakeMesub.webhook` builds the body for you.\n */\nexport async function signWebhook(\n payload: unknown,\n options: SignWebhookOptions,\n): Promise<SignedWebhook> {\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload);\n const id = options.id ?? `msg_signed_${(signedIds += 1)}`;\n const timestamp = options.timestamp ?? Math.floor(Date.now() / 1000);\n\n return { body, headers: await signedHeaders(options.secret, id, timestamp, body) };\n}\n\n/** A plausible detail for each type that carries one, as the back writes it. */\nfunction detailOf(\n type: WebhookEventType,\n now: Date,\n data: ServerSubscription,\n): Record<string, unknown> {\n const start = now.toISOString();\n const end = new Date(now.getTime() + PERIOD_MS).toISOString();\n const paid = { amount: '9990000', mint: USDC, period_start: start, period_end: end };\n\n switch (type) {\n case 'subscription.renewal_upcoming':\n return {\n can_pay: true,\n renewal_issue: null,\n amount: paid.amount,\n mint: USDC,\n // The check's window opens three days before the charge at most.\n due_at: new Date(now.getTime() + 3 * 24 * 3600 * 1000).toISOString(),\n };\n case 'subscription.renewed':\n return { ...paid, signature: 'fake_signature' };\n case 'subscription.payment_failed':\n return {\n ...paid,\n reason: 'insufficient_balance',\n next_retry_at: new Date(now.getTime() + 24 * 3600 * 1000).toISOString(),\n retry_deadline: null,\n retries_left: 1,\n retry_mode: 'scheduled',\n };\n case 'subscription.stopped':\n return { reason: 'insufficient_balance' };\n case 'subscription.access_granted':\n case 'subscription.access_revoked':\n return accessDetailOf(data, start);\n case 'subscription.created':\n return {\n first_payment: { amount: paid.amount, mint: USDC, signature: 'fake_signature' },\n };\n case 'subscription.ended':\n // A cancellation reaching its end says when (Mesub-io/backend#509).\n return data.end_reason === 'cancelled'\n ? { changed_at: start, end_reason: 'cancelled' }\n : {};\n default:\n return {};\n }\n}\n\n/**\n * An access event's detail as Mesub writes it (`accessDetail` and\n * `accessCauseOf`, Mesub-io/backend#511): when, the hold on its charges, and\n * the date access ends. A grant names that date only on a held subscription;\n * a revocation names the date that passed (a held one's `access_until`, the\n * end of a cancellation or of the plan), and none for a stop or a close.\n */\nfunction accessDetailOf(data: ServerSubscription, changedAt: string): Record<string, unknown> {\n const { access, blocked_reason, access_until, status, end_reason } = data;\n const dated =\n blocked_reason !== null ||\n (status === 'ended' && (end_reason === 'cancelled' || end_reason === 'plan_ended'));\n\n return {\n changed_at: changedAt,\n blocked_reason,\n access_until: access\n ? blocked_reason === null\n ? null\n : access_until\n : (access_until ?? (dated ? changedAt : null)),\n };\n}\n\n/**\n * The made-up subscription a type carries unless a test says otherwise: an\n * active, paid one, but for `access_revoked`, whose `data.access` is false:\n * a cancellation that reached its end.\n */\nfunction subscriptionOf(type: WebhookEventType): Partial<ServerSubscription> {\n if (type === 'subscription.paused') return { paused: true, next_charge_at: null };\n if (type !== 'subscription.access_revoked') return {};\n\n return {\n status: 'ended',\n end_reason: 'cancelled',\n access: false,\n payment_status: 'none',\n next_charge_at: null,\n access_until: null,\n };\n}\n\nfunction nothing(asked: Asked, plan: string): Held {\n return { wallet: asked.kind === 'wallet' ? asked.value : null, plan, ...NOTHING };\n}\n\n/** A Solana address's shape, as Mesub's DTOs hold a wallet to: base58, 32 to 44 characters. */\nconst BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n\n/**\n * Close to the `IsEmail` Mesub checks an email with, validator.js's\n * defaults: no space, one @, and a domain ending in a top-level one of two\n * letters or more, so `ops@localhost` is refused. Not every edge of it.\n */\nconst EMAIL = /^[^\\s@]+@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z]{2,}$/;\n\n/**\n * Mesub's 400 for a wallet or an email its DTOs refuse (`CustomerQueryDto`,\n * `ServerSubscribeDto`), before anything is looked up; null for those it\n * takes, or that are not given. The email is trimmed and lowercased first,\n * as Mesub does.\n */\nfunction invalidContact({ wallet, email }: { wallet?: unknown; email?: unknown }): Response | null {\n const reasons: string[] = [];\n if (typeof wallet === 'string' && !BASE58_ADDRESS.test(wallet)) {\n reasons.push('wallet must be a base58 Solana address');\n }\n if (typeof email === 'string') {\n const normalised = email.trim().toLowerCase();\n if (!EMAIL.test(normalised)) reasons.push('email must be an email address');\n if (normalised.length > 254) {\n reasons.push('email must be shorter than or equal to 254 characters');\n }\n }\n\n return reasons.length ? error(400, 'invalid_request', reasons) : null;\n}\n\nconst LIMIT_MESSAGE = 'limit must be a whole number from 1 to 100';\n\n/** A list's `limit` as Mesub reads it (`asInteger`, then 1 to 100), null when it refuses it. */\nfunction limitOf(value: string): number | null {\n const limit = /^\\d{1,4}$/.test(value) ? Number(value) : NaN;\n\n return limit >= 1 && limit <= 100 ? limit : null;\n}\n\n/**\n * Mesub's 400 for a submit body its `ServerSubmitDto` refuses, null for one\n * it takes. The transaction is held to base64, as there; `terms_signature`\n * only to a string that is not empty, where Mesub holds it to 64 bytes in\n * base58: the fake checks no signature, so a test may hand it any.\n */\nfunction invalidSubmit(body: unknown): Response | null {\n const { transaction, terms_signature } = (body ?? {}) as Record<string, unknown>;\n const reasons: string[] = [];\n\n if (typeof transaction !== 'string' || transaction.length > 2048) {\n reasons.push('transaction must be shorter than or equal to 2048 characters');\n }\n if (typeof transaction !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(transaction)) {\n reasons.push('transaction must be base64');\n }\n if (typeof terms_signature !== 'string' || terms_signature === '') {\n reasons.push('terms_signature must be a base58 signature');\n }\n\n return reasons.length ? error(400, 'invalid_request', reasons) : null;\n}\n\n/** The one customer a query names, or null when it names none or several. */\nfunction askedIn(query: Record<string, string>): Asked | null {\n try {\n return customerOf({\n wallet: query['wallet'],\n external_id: query['external_id'],\n email: query['email'],\n } as unknown as Customer);\n } catch {\n return null;\n }\n}\n\nfunction withAttempts(answer: AccessAnswer, attempts: boolean): AccessAnswer {\n if (attempts) return { ...answer, attempts: answer.attempts ?? [] };\n\n const light = { ...answer };\n delete light.attempts;\n return light;\n}\n\n/**\n * A plan as `/v1/plans` serves one: 9.99 USDC a month on mainnet, unless told\n * otherwise. Its token follows its `symbol`, which only a vouched mint has:\n * verified and worth its amount in dollars with one, unverified and unpriced\n * with a `token` given or a null `symbol`.\n */\nfunction fakePlan(slug: string, { token, ...over }: Omit<FakePlanFields, 'slug'> = {}): Plan {\n const symbol = over.symbol === undefined ? (token ? null : 'USDC') : over.symbol;\n const vouched = token === undefined && symbol !== null;\n\n return {\n slug,\n name: slug,\n description: null,\n project_name: 'Test project',\n logo_url: null,\n amount: '9990000',\n amount_display: '9.99',\n amount_usd: vouched ? (over.amount_display ?? '9.99') : null,\n decimals: 6,\n symbol,\n token: {\n symbol: token ? null : symbol,\n name: vouched && symbol === 'USDC' ? 'USD Coin' : null,\n logo_url: null,\n verified: vouched,\n ...token,\n },\n mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n period_hours: 720,\n network: 'mainnet-beta',\n status: 'active',\n available: true,\n ends_at: null,\n ...over,\n };\n}\n\n/**\n * The codes Mesub answers `retryable: true`, from the back's `ERROR_CODES`,\n * which sets the flag by code alone. Every other code it has is false, and\n * none of those is ever answered as a 408, a 429 or a 5xx, so the status\n * says the same of them.\n */\nconst RETRYABLE_CODES = new Set([\n 'rate_limited',\n 'internal_error',\n 'unavailable',\n 'comeback_in_flight',\n 'comeback_period_rolling',\n 'pending_cap_reached',\n 'subscription_changed',\n 'retry_too_soon',\n 'pull_running',\n 'network_unavailable',\n]);\n\n/**\n * The `Retry-After` Mesub sends with a 503 on a route that builds, sends or\n * queues: its spec makes it required there. The seat wait of a submit\n * (`SEAT_BUSY_RETRY_AFTER`), the queue of a retry (`UNQUEUED_RETRY_AFTER`),\n * the chain guard's otherwise. And the one `retry_too_soon` always carries.\n * Undefined elsewhere.\n */\nfunction waitOf(\n method: string,\n path: string,\n status: number,\n code: string,\n tier: FakeTier,\n): number | undefined {\n // Always sent with its wait: the first one of the tier, as no retry was counted.\n if (code === 'retry_too_soon')\n return (tier === 'free' ? FREE_RETRY_WAIT_MS : PAID_RETRY_WAIT_MS) / 1000;\n if (status !== 503 || method !== 'POST' || !path.startsWith('/v1/subscriptions')) {\n return undefined;\n }\n if (code === 'unavailable' && path.endsWith('/submit')) return 2;\n if (code === 'unavailable' && path.endsWith('/retry')) return 20;\n return 10;\n}\n\n/** An error as the back writes it: Nest's body, plus `code` and `retryable`. */\nfunction error(\n status: number,\n code: string,\n // A list for what Mesub's validation refused, as Nest answers it.\n message: string | string[],\n { retryable, retryAfter }: { retryable?: boolean; retryAfter?: number } = {},\n): Response {\n return Response.json(\n {\n statusCode: status,\n error: REASONS[status] ?? 'Error',\n message,\n code,\n retryable:\n retryable ??\n (RETRYABLE_CODES.has(code) || status === 408 || status === 429 || status >= 500),\n },\n {\n status,\n ...(retryAfter !== undefined && { headers: { 'Retry-After': String(retryAfter) } }),\n },\n );\n}\n\n/**\n * The answer with the id Mesub gives every request (Mesub-io/backend#472): in\n * `Mesub-Request-Id`, and in an error body's `request_id`. And the version it\n * is written in, `Mesub-Version`, on every answer, errors included: the one\n * served, also for a call that named none or one not served.\n */\nasync function stamped(response: Response, id: string): Promise<Response> {\n const headers = new Headers(response.headers);\n headers.set(REQUEST_ID_HEADER, id);\n headers.set(API_VERSION_HEADER, API_VERSION);\n\n if (response.ok) return new Response(response.body, { status: response.status, headers });\n\n const body = (await response.json()) as Record<string, unknown>;\n return Response.json({ ...body, request_id: id }, { status: response.status, headers });\n}\n"],"mappings":"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","names":["answer","nothing"]}
|