@mesub/node 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +835 -2
- package/dist/chunk-43Q4PNF7.cjs +480 -0
- package/dist/chunk-43Q4PNF7.cjs.map +1 -0
- package/dist/chunk-ELK7IBLO.js +340 -0
- package/dist/chunk-ELK7IBLO.js.map +1 -0
- package/dist/chunk-JJWHMB2X.js +480 -0
- package/dist/chunk-JJWHMB2X.js.map +1 -0
- package/dist/chunk-KMPBFNJE.js +2038 -0
- package/dist/chunk-KMPBFNJE.js.map +1 -0
- package/dist/chunk-PCDZI7B4.cjs +340 -0
- package/dist/chunk-PCDZI7B4.cjs.map +1 -0
- package/dist/chunk-SGSYQJYT.cjs +2038 -0
- package/dist/chunk-SGSYQJYT.cjs.map +1 -0
- package/dist/chunk-VMKI4VVA.cjs +120 -0
- package/dist/chunk-VMKI4VVA.cjs.map +1 -0
- package/dist/chunk-XDLLXY7Y.js +120 -0
- package/dist/chunk-XDLLXY7Y.js.map +1 -0
- package/dist/client-BQuRYSdB.d.ts +759 -0
- package/dist/client-Dm1jamju.d.cts +759 -0
- package/dist/errors-CtPucyOh.d.cts +112 -0
- package/dist/errors-DqW3DMBS.d.ts +112 -0
- package/dist/express.cjs +129 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +57 -0
- package/dist/express.d.ts +57 -0
- package/dist/express.js +129 -0
- package/dist/express.js.map +1 -0
- package/dist/guard-DKb6iU1z.d.ts +51 -0
- package/dist/guard-Dhy4fI6n.d.cts +51 -0
- package/dist/index.cjs +39 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/nest.cjs +62 -0
- package/dist/nest.cjs.map +1 -0
- package/dist/nest.d.cts +57 -0
- package/dist/nest.d.ts +57 -0
- package/dist/nest.js +62 -0
- package/dist/nest.js.map +1 -0
- package/dist/next.cjs +113 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +63 -0
- package/dist/next.d.ts +63 -0
- package/dist/next.js +113 -0
- package/dist/next.js.map +1 -0
- package/dist/routes-CLtZnUzl.d.ts +130 -0
- package/dist/routes-DWkRFOP0.d.cts +130 -0
- package/dist/situations.cjs +15 -0
- package/dist/situations.cjs.map +1 -0
- package/dist/situations.d.cts +607 -0
- package/dist/situations.d.ts +607 -0
- package/dist/situations.js +15 -0
- package/dist/situations.js.map +1 -0
- package/dist/subscriptions-D1hE7MUc.d.cts +768 -0
- package/dist/subscriptions-D1hE7MUc.d.ts +768 -0
- package/dist/testing.cjs +1486 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +331 -0
- package/dist/testing.d.ts +331 -0
- package/dist/testing.js +1486 -0
- package/dist/testing.js.map +1 -0
- package/package.json +158 -4
package/dist/next.cjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunkPCDZI7B4cjs = require('./chunk-PCDZI7B4.cjs');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
var _chunkVMKI4VVAcjs = require('./chunk-VMKI4VVA.cjs');
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
var _chunkSGSYQJYTcjs = require('./chunk-SGSYQJYT.cjs');
|
|
22
|
+
|
|
23
|
+
// src/next.ts
|
|
24
|
+
function withMesub(handler, options) {
|
|
25
|
+
_chunkVMKI4VVAcjs.checkPlan.call(void 0, options.plan);
|
|
26
|
+
_chunkVMKI4VVAcjs.checkCustomer.call(void 0, options);
|
|
27
|
+
return async (request, context) => {
|
|
28
|
+
const client = _nullishCoalesce(options.client, () => ( _chunkVMKI4VVAcjs.defaultClient.call(void 0, )));
|
|
29
|
+
const outcome = await _chunkVMKI4VVAcjs.guard.call(void 0,
|
|
30
|
+
client,
|
|
31
|
+
await _chunkVMKI4VVAcjs.askerOf.call(void 0, request, options.customer),
|
|
32
|
+
() => _chunkVMKI4VVAcjs.plansOf.call(void 0, options.plan, request)
|
|
33
|
+
);
|
|
34
|
+
if (outcome.allowed) return handler(request, _chunkVMKI4VVAcjs.accessOf.call(void 0, outcome), context);
|
|
35
|
+
const denial = _chunkVMKI4VVAcjs.denialOf.call(void 0, outcome);
|
|
36
|
+
if (options.onDenied) return await options.onDenied(denial, request);
|
|
37
|
+
const headers = outcome.reason === "unavailable" ? { "Retry-After": _chunkVMKI4VVAcjs.retryAfterOf.call(void 0, outcome) } : {};
|
|
38
|
+
return Response.json(_chunkVMKI4VVAcjs.denialBody.call(void 0, outcome), { status: denial.status, headers });
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function pathOf(context) {
|
|
42
|
+
const params = await _asyncNullishCoalesce(await _optionalChain([context, 'optionalAccess', _ => _.params]), async () => ( {}));
|
|
43
|
+
const segments = Object.values(params).find((value) => Array.isArray(value));
|
|
44
|
+
return `/${(_nullishCoalesce(segments, () => ( []))).map((segment) => encodeURIComponent(String(segment))).join("/")}`;
|
|
45
|
+
}
|
|
46
|
+
async function textOf(request) {
|
|
47
|
+
if (Number(request.headers.get("content-length")) > _chunkPCDZI7B4cjs.MAX_BODY_BYTES) return null;
|
|
48
|
+
if (request.body === null) return "";
|
|
49
|
+
const reader = request.body.getReader();
|
|
50
|
+
const decoder = new TextDecoder();
|
|
51
|
+
let text = "";
|
|
52
|
+
let size = 0;
|
|
53
|
+
for (; ; ) {
|
|
54
|
+
const { done, value } = await reader.read();
|
|
55
|
+
if (done) return text + decoder.decode();
|
|
56
|
+
size += value.byteLength;
|
|
57
|
+
if (size > _chunkPCDZI7B4cjs.MAX_BODY_BYTES) {
|
|
58
|
+
await reader.cancel();
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
text += decoder.decode(value, { stream: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function mesubRouteHandlers(options) {
|
|
65
|
+
_chunkPCDZI7B4cjs.checkWidgetOptions.call(void 0, options);
|
|
66
|
+
const handle = async (request, context) => {
|
|
67
|
+
const path = await pathOf(context);
|
|
68
|
+
let body;
|
|
69
|
+
if (request.method !== "GET") {
|
|
70
|
+
const text = await textOf(request);
|
|
71
|
+
if (text === null) {
|
|
72
|
+
return Response.json(
|
|
73
|
+
{ error: { code: "payload_too_large", message: "The body is too large." } },
|
|
74
|
+
{ status: 413 }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
body = text === "" ? void 0 : JSON.parse(text);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return Response.json(
|
|
81
|
+
{ error: { code: "invalid_request", message: "The body is not JSON." } },
|
|
82
|
+
{ status: 400 }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const publicRead = request.method === "GET" && path.startsWith("/plans/");
|
|
87
|
+
const asked = publicRead ? null : await _chunkPCDZI7B4cjs.widgetCustomer.call(void 0, request, options.customer);
|
|
88
|
+
const email = asked && options.email ? await options.email(request) : void 0;
|
|
89
|
+
const answer = await _chunkPCDZI7B4cjs.handleWidget.call(void 0,
|
|
90
|
+
_nullishCoalesce(options.client, () => ( _chunkVMKI4VVAcjs.defaultClient.call(void 0, ))),
|
|
91
|
+
{
|
|
92
|
+
method: request.method,
|
|
93
|
+
path,
|
|
94
|
+
body,
|
|
95
|
+
contentType: request.headers.get("content-type")
|
|
96
|
+
},
|
|
97
|
+
asked,
|
|
98
|
+
{ email, ...options.plans && { plans: options.plans } }
|
|
99
|
+
);
|
|
100
|
+
return Response.json(answer.body, {
|
|
101
|
+
status: answer.status,
|
|
102
|
+
// Per customer, and it moves: never a shared cache's to keep.
|
|
103
|
+
headers: { "Cache-Control": "no-store", ...answer.headers }
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
return { GET: handle, POST: handle };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
exports.MesubError = _chunkSGSYQJYTcjs.MesubError; exports.mesubRouteHandlers = mesubRouteHandlers; exports.withMesub = withMesub;
|
|
113
|
+
//# sourceMappingURL=next.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/node-sdk/node-sdk/dist/next.cjs","../src/next.ts"],"names":[],"mappings":"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","file":"/home/runner/work/node-sdk/node-sdk/dist/next.cjs","sourcesContent":[null,"/** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */\nimport type { Mesub } from './client.js';\nimport {\n accessOf,\n askerOf,\n checkCustomer,\n checkPlan,\n type CustomerOption,\n type Denial,\n defaultClient,\n denialBody,\n denialOf,\n guard,\n type MesubAccess,\n type PlanOption,\n plansOf,\n retryAfterOf,\n} from './guard.js';\n\nimport {\n checkWidgetOptions,\n handleWidget,\n MAX_BODY_BYTES,\n widgetCustomer,\n type WidgetRoutesOptions,\n} from './routes.js';\n\nexport { MesubError } from './errors.js';\nexport type {\n WidgetPayment,\n WidgetRoutesOptions,\n WidgetSubscriptionDetail,\n WidgetSubscriptionList,\n} from './routes.js';\nexport type { CustomerOption, Denial, DenialReason, MesubAccess, PlanOption } from './guard.js';\nexport type { Asked } from './customer.js';\nexport type { Customer } from './answer.js';\n\nexport interface WithMesubOptions {\n /**\n * A slug, a list of which any one will do (`['pro', 'team']`), or either\n * worked out per request. `mesub.plan` says which one let it through.\n */\n plan: PlanOption<Request>;\n /** Defaults to one client built from MESUB_API_KEY. */\n client?: Mesub;\n /**\n * Who is asking, from your own auth:\n * `(req) => ({ external_id: session.userId })`, a wallet or an email. Null when\n * nobody is signed in. It must come from a session you verified, never\n * from the request itself.\n */\n customer: CustomerOption<Request>;\n /**\n * Answer a refusal yourself: a redirect, a page, your own JSON. It may be\n * async; a throw or a rejection is thrown, for Next to answer 500.\n */\n onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;\n}\n\n/** Your route handler, with who is asking and Mesub's answer as the second argument. */\nexport type MesubRouteHandler<Context = unknown> = (\n request: Request,\n mesub: MesubAccess,\n context: Context,\n) => Response | Promise<Response>;\n\n/**\n * Wraps an App Router route handler so it runs only for a subscriber with\n * access to `plan`. Next's `context` (with `params`) is passed through as is.\n *\n * Who is asking is who `customer` says, from your own auth. Refusals answer\n * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub\n * unreachable, with no answer known for that subscriber). Integration errors\n * are thrown, for Next to log and answer 500. Not for `middleware.ts`, server\n * components or pages.\n */\nexport function withMesub<Context = unknown>(\n handler: MesubRouteHandler<Context>,\n options: WithMesubOptions,\n): (request: Request, context: Context) => Promise<Response> {\n checkPlan(options.plan);\n checkCustomer(options);\n\n return async (request, context) => {\n const client = options.client ?? defaultClient();\n const outcome = await guard(client, await askerOf(request, options.customer), () =>\n plansOf(options.plan, request),\n );\n\n if (outcome.allowed) return handler(request, accessOf(outcome), context);\n\n const denial = denialOf(outcome);\n\n if (options.onDenied) return await options.onDenied(denial, request);\n\n const headers: Record<string, string> =\n outcome.reason === 'unavailable' ? { 'Retry-After': retryAfterOf(outcome) } : {};\n\n return Response.json(denialBody(outcome), { status: denial.status, headers });\n };\n}\n\n/** Next's `context` of a catch-all route: `params` is a promise since Next 15. */\ninterface CatchAllContext {\n params?: Promise<Record<string, unknown>> | Record<string, unknown>;\n}\n\n/** The path after the mount point, from the catch-all segment: `/subscriptions/sub_1/cancel`. */\nasync function pathOf(context: CatchAllContext | undefined): Promise<string> {\n const params = (await context?.params) ?? {};\n const segments = Object.values(params).find((value) => Array.isArray(value)) as\n unknown[] | undefined;\n\n return `/${(segments ?? []).map((segment) => encodeURIComponent(String(segment))).join('/')}`;\n}\n\n/**\n * The body as text, read up to the limit and no further. Null past it, by\n * `Content-Length` before anything is read, else by the bytes counted.\n */\nasync function textOf(request: Request): Promise<string | null> {\n if (Number(request.headers.get('content-length')) > MAX_BODY_BYTES) return null;\n if (request.body === null) return '';\n\n const reader = request.body.getReader();\n const decoder = new TextDecoder();\n let text = '';\n let size = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) return text + decoder.decode();\n\n size += value.byteLength;\n if (size > MAX_BODY_BYTES) {\n await reader.cancel();\n return null;\n }\n text += decoder.decode(value, { stream: true });\n }\n}\n\n/**\n * The routes `@mesub/react` calls, as an App Router catch-all:\n *\n * // app/api/mesub/[...mesub]/route.ts\n * export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });\n *\n * `customer` says who is asking from your own verified session. Integration\n * errors are thrown, for Next to log and answer 500.\n */\nexport function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {\n GET: (request: Request, context?: CatchAllContext) => Promise<Response>;\n POST: (request: Request, context?: CatchAllContext) => Promise<Response>;\n} {\n checkWidgetOptions(options);\n\n const handle = async (request: Request, context?: CatchAllContext): Promise<Response> => {\n const path = await pathOf(context);\n let body: unknown;\n\n if (request.method !== 'GET') {\n const text = await textOf(request);\n\n if (text === null) {\n return Response.json(\n { error: { code: 'payload_too_large', message: 'The body is too large.' } },\n { status: 413 },\n );\n }\n try {\n body = text === '' ? undefined : (JSON.parse(text) as unknown);\n } catch {\n return Response.json(\n { error: { code: 'invalid_request', message: 'The body is not JSON.' } },\n { status: 400 },\n );\n }\n }\n\n // A plan is public: nobody needs to be signed in to read a price.\n const publicRead = request.method === 'GET' && path.startsWith('/plans/');\n const asked = publicRead ? null : await widgetCustomer(request, options.customer);\n const email = asked && options.email ? await options.email(request) : undefined;\n const answer = await handleWidget(\n options.client ?? defaultClient(),\n {\n method: request.method,\n path,\n body,\n contentType: request.headers.get('content-type'),\n },\n asked,\n { email, ...(options.plans && { plans: options.plans }) },\n );\n\n return Response.json(answer.body, {\n status: answer.status,\n // Per customer, and it moves: never a shared cache's to keep.\n headers: { 'Cache-Control': 'no-store', ...answer.headers },\n });\n };\n\n return { GET: handle, POST: handle };\n}\n"]}
|
package/dist/next.d.cts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { M as Mesub } from './client-Dm1jamju.cjs';
|
|
2
|
+
import { M as MesubAccess, P as PlanOption, C as CustomerOption, D as Denial } from './guard-Dhy4fI6n.cjs';
|
|
3
|
+
export { A as Asked, a as DenialReason } from './guard-Dhy4fI6n.cjs';
|
|
4
|
+
import { W as WidgetRoutesOptions } from './routes-DWkRFOP0.cjs';
|
|
5
|
+
export { a as WidgetPayment, b as WidgetSubscriptionDetail, c as WidgetSubscriptionList } from './routes-DWkRFOP0.cjs';
|
|
6
|
+
export { M as MesubError } from './errors-CtPucyOh.cjs';
|
|
7
|
+
export { C as Customer } from './subscriptions-D1hE7MUc.cjs';
|
|
8
|
+
|
|
9
|
+
/** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */
|
|
10
|
+
|
|
11
|
+
interface WithMesubOptions {
|
|
12
|
+
/**
|
|
13
|
+
* A slug, a list of which any one will do (`['pro', 'team']`), or either
|
|
14
|
+
* worked out per request. `mesub.plan` says which one let it through.
|
|
15
|
+
*/
|
|
16
|
+
plan: PlanOption<Request>;
|
|
17
|
+
/** Defaults to one client built from MESUB_API_KEY. */
|
|
18
|
+
client?: Mesub;
|
|
19
|
+
/**
|
|
20
|
+
* Who is asking, from your own auth:
|
|
21
|
+
* `(req) => ({ external_id: session.userId })`, a wallet or an email. Null when
|
|
22
|
+
* nobody is signed in. It must come from a session you verified, never
|
|
23
|
+
* from the request itself.
|
|
24
|
+
*/
|
|
25
|
+
customer: CustomerOption<Request>;
|
|
26
|
+
/**
|
|
27
|
+
* Answer a refusal yourself: a redirect, a page, your own JSON. It may be
|
|
28
|
+
* async; a throw or a rejection is thrown, for Next to answer 500.
|
|
29
|
+
*/
|
|
30
|
+
onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;
|
|
31
|
+
}
|
|
32
|
+
/** Your route handler, with who is asking and Mesub's answer as the second argument. */
|
|
33
|
+
type MesubRouteHandler<Context = unknown> = (request: Request, mesub: MesubAccess, context: Context) => Response | Promise<Response>;
|
|
34
|
+
/**
|
|
35
|
+
* Wraps an App Router route handler so it runs only for a subscriber with
|
|
36
|
+
* access to `plan`. Next's `context` (with `params`) is passed through as is.
|
|
37
|
+
*
|
|
38
|
+
* Who is asking is who `customer` says, from your own auth. Refusals answer
|
|
39
|
+
* 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub
|
|
40
|
+
* unreachable, with no answer known for that subscriber). Integration errors
|
|
41
|
+
* are thrown, for Next to log and answer 500. Not for `middleware.ts`, server
|
|
42
|
+
* components or pages.
|
|
43
|
+
*/
|
|
44
|
+
declare function withMesub<Context = unknown>(handler: MesubRouteHandler<Context>, options: WithMesubOptions): (request: Request, context: Context) => Promise<Response>;
|
|
45
|
+
/** Next's `context` of a catch-all route: `params` is a promise since Next 15. */
|
|
46
|
+
interface CatchAllContext {
|
|
47
|
+
params?: Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The routes `@mesub/react` calls, as an App Router catch-all:
|
|
51
|
+
*
|
|
52
|
+
* // app/api/mesub/[...mesub]/route.ts
|
|
53
|
+
* export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });
|
|
54
|
+
*
|
|
55
|
+
* `customer` says who is asking from your own verified session. Integration
|
|
56
|
+
* errors are thrown, for Next to log and answer 500.
|
|
57
|
+
*/
|
|
58
|
+
declare function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {
|
|
59
|
+
GET: (request: Request, context?: CatchAllContext) => Promise<Response>;
|
|
60
|
+
POST: (request: Request, context?: CatchAllContext) => Promise<Response>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { CustomerOption, Denial, MesubAccess, type MesubRouteHandler, PlanOption, WidgetRoutesOptions, type WithMesubOptions, mesubRouteHandlers, withMesub };
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { M as Mesub } from './client-BQuRYSdB.js';
|
|
2
|
+
import { M as MesubAccess, P as PlanOption, C as CustomerOption, D as Denial } from './guard-DKb6iU1z.js';
|
|
3
|
+
export { A as Asked, a as DenialReason } from './guard-DKb6iU1z.js';
|
|
4
|
+
import { W as WidgetRoutesOptions } from './routes-CLtZnUzl.js';
|
|
5
|
+
export { a as WidgetPayment, b as WidgetSubscriptionDetail, c as WidgetSubscriptionList } from './routes-CLtZnUzl.js';
|
|
6
|
+
export { M as MesubError } from './errors-DqW3DMBS.js';
|
|
7
|
+
export { C as Customer } from './subscriptions-D1hE7MUc.js';
|
|
8
|
+
|
|
9
|
+
/** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */
|
|
10
|
+
|
|
11
|
+
interface WithMesubOptions {
|
|
12
|
+
/**
|
|
13
|
+
* A slug, a list of which any one will do (`['pro', 'team']`), or either
|
|
14
|
+
* worked out per request. `mesub.plan` says which one let it through.
|
|
15
|
+
*/
|
|
16
|
+
plan: PlanOption<Request>;
|
|
17
|
+
/** Defaults to one client built from MESUB_API_KEY. */
|
|
18
|
+
client?: Mesub;
|
|
19
|
+
/**
|
|
20
|
+
* Who is asking, from your own auth:
|
|
21
|
+
* `(req) => ({ external_id: session.userId })`, a wallet or an email. Null when
|
|
22
|
+
* nobody is signed in. It must come from a session you verified, never
|
|
23
|
+
* from the request itself.
|
|
24
|
+
*/
|
|
25
|
+
customer: CustomerOption<Request>;
|
|
26
|
+
/**
|
|
27
|
+
* Answer a refusal yourself: a redirect, a page, your own JSON. It may be
|
|
28
|
+
* async; a throw or a rejection is thrown, for Next to answer 500.
|
|
29
|
+
*/
|
|
30
|
+
onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;
|
|
31
|
+
}
|
|
32
|
+
/** Your route handler, with who is asking and Mesub's answer as the second argument. */
|
|
33
|
+
type MesubRouteHandler<Context = unknown> = (request: Request, mesub: MesubAccess, context: Context) => Response | Promise<Response>;
|
|
34
|
+
/**
|
|
35
|
+
* Wraps an App Router route handler so it runs only for a subscriber with
|
|
36
|
+
* access to `plan`. Next's `context` (with `params`) is passed through as is.
|
|
37
|
+
*
|
|
38
|
+
* Who is asking is who `customer` says, from your own auth. Refusals answer
|
|
39
|
+
* 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub
|
|
40
|
+
* unreachable, with no answer known for that subscriber). Integration errors
|
|
41
|
+
* are thrown, for Next to log and answer 500. Not for `middleware.ts`, server
|
|
42
|
+
* components or pages.
|
|
43
|
+
*/
|
|
44
|
+
declare function withMesub<Context = unknown>(handler: MesubRouteHandler<Context>, options: WithMesubOptions): (request: Request, context: Context) => Promise<Response>;
|
|
45
|
+
/** Next's `context` of a catch-all route: `params` is a promise since Next 15. */
|
|
46
|
+
interface CatchAllContext {
|
|
47
|
+
params?: Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The routes `@mesub/react` calls, as an App Router catch-all:
|
|
51
|
+
*
|
|
52
|
+
* // app/api/mesub/[...mesub]/route.ts
|
|
53
|
+
* export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });
|
|
54
|
+
*
|
|
55
|
+
* `customer` says who is asking from your own verified session. Integration
|
|
56
|
+
* errors are thrown, for Next to log and answer 500.
|
|
57
|
+
*/
|
|
58
|
+
declare function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {
|
|
59
|
+
GET: (request: Request, context?: CatchAllContext) => Promise<Response>;
|
|
60
|
+
POST: (request: Request, context?: CatchAllContext) => Promise<Response>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { CustomerOption, Denial, MesubAccess, type MesubRouteHandler, PlanOption, WidgetRoutesOptions, type WithMesubOptions, mesubRouteHandlers, withMesub };
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_BODY_BYTES,
|
|
3
|
+
checkWidgetOptions,
|
|
4
|
+
handleWidget,
|
|
5
|
+
widgetCustomer
|
|
6
|
+
} from "./chunk-ELK7IBLO.js";
|
|
7
|
+
import {
|
|
8
|
+
accessOf,
|
|
9
|
+
askerOf,
|
|
10
|
+
checkCustomer,
|
|
11
|
+
checkPlan,
|
|
12
|
+
defaultClient,
|
|
13
|
+
denialBody,
|
|
14
|
+
denialOf,
|
|
15
|
+
guard,
|
|
16
|
+
plansOf,
|
|
17
|
+
retryAfterOf
|
|
18
|
+
} from "./chunk-XDLLXY7Y.js";
|
|
19
|
+
import {
|
|
20
|
+
MesubError
|
|
21
|
+
} from "./chunk-KMPBFNJE.js";
|
|
22
|
+
|
|
23
|
+
// src/next.ts
|
|
24
|
+
function withMesub(handler, options) {
|
|
25
|
+
checkPlan(options.plan);
|
|
26
|
+
checkCustomer(options);
|
|
27
|
+
return async (request, context) => {
|
|
28
|
+
const client = options.client ?? defaultClient();
|
|
29
|
+
const outcome = await guard(
|
|
30
|
+
client,
|
|
31
|
+
await askerOf(request, options.customer),
|
|
32
|
+
() => plansOf(options.plan, request)
|
|
33
|
+
);
|
|
34
|
+
if (outcome.allowed) return handler(request, accessOf(outcome), context);
|
|
35
|
+
const denial = denialOf(outcome);
|
|
36
|
+
if (options.onDenied) return await options.onDenied(denial, request);
|
|
37
|
+
const headers = outcome.reason === "unavailable" ? { "Retry-After": retryAfterOf(outcome) } : {};
|
|
38
|
+
return Response.json(denialBody(outcome), { status: denial.status, headers });
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function pathOf(context) {
|
|
42
|
+
const params = await context?.params ?? {};
|
|
43
|
+
const segments = Object.values(params).find((value) => Array.isArray(value));
|
|
44
|
+
return `/${(segments ?? []).map((segment) => encodeURIComponent(String(segment))).join("/")}`;
|
|
45
|
+
}
|
|
46
|
+
async function textOf(request) {
|
|
47
|
+
if (Number(request.headers.get("content-length")) > MAX_BODY_BYTES) return null;
|
|
48
|
+
if (request.body === null) return "";
|
|
49
|
+
const reader = request.body.getReader();
|
|
50
|
+
const decoder = new TextDecoder();
|
|
51
|
+
let text = "";
|
|
52
|
+
let size = 0;
|
|
53
|
+
for (; ; ) {
|
|
54
|
+
const { done, value } = await reader.read();
|
|
55
|
+
if (done) return text + decoder.decode();
|
|
56
|
+
size += value.byteLength;
|
|
57
|
+
if (size > MAX_BODY_BYTES) {
|
|
58
|
+
await reader.cancel();
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
text += decoder.decode(value, { stream: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function mesubRouteHandlers(options) {
|
|
65
|
+
checkWidgetOptions(options);
|
|
66
|
+
const handle = async (request, context) => {
|
|
67
|
+
const path = await pathOf(context);
|
|
68
|
+
let body;
|
|
69
|
+
if (request.method !== "GET") {
|
|
70
|
+
const text = await textOf(request);
|
|
71
|
+
if (text === null) {
|
|
72
|
+
return Response.json(
|
|
73
|
+
{ error: { code: "payload_too_large", message: "The body is too large." } },
|
|
74
|
+
{ status: 413 }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
body = text === "" ? void 0 : JSON.parse(text);
|
|
79
|
+
} catch {
|
|
80
|
+
return Response.json(
|
|
81
|
+
{ error: { code: "invalid_request", message: "The body is not JSON." } },
|
|
82
|
+
{ status: 400 }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const publicRead = request.method === "GET" && path.startsWith("/plans/");
|
|
87
|
+
const asked = publicRead ? null : await widgetCustomer(request, options.customer);
|
|
88
|
+
const email = asked && options.email ? await options.email(request) : void 0;
|
|
89
|
+
const answer = await handleWidget(
|
|
90
|
+
options.client ?? defaultClient(),
|
|
91
|
+
{
|
|
92
|
+
method: request.method,
|
|
93
|
+
path,
|
|
94
|
+
body,
|
|
95
|
+
contentType: request.headers.get("content-type")
|
|
96
|
+
},
|
|
97
|
+
asked,
|
|
98
|
+
{ email, ...options.plans && { plans: options.plans } }
|
|
99
|
+
);
|
|
100
|
+
return Response.json(answer.body, {
|
|
101
|
+
status: answer.status,
|
|
102
|
+
// Per customer, and it moves: never a shared cache's to keep.
|
|
103
|
+
headers: { "Cache-Control": "no-store", ...answer.headers }
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
return { GET: handle, POST: handle };
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
MesubError,
|
|
110
|
+
mesubRouteHandlers,
|
|
111
|
+
withMesub
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=next.js.map
|
package/dist/next.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next.ts"],"sourcesContent":["/** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */\nimport type { Mesub } from './client.js';\nimport {\n accessOf,\n askerOf,\n checkCustomer,\n checkPlan,\n type CustomerOption,\n type Denial,\n defaultClient,\n denialBody,\n denialOf,\n guard,\n type MesubAccess,\n type PlanOption,\n plansOf,\n retryAfterOf,\n} from './guard.js';\n\nimport {\n checkWidgetOptions,\n handleWidget,\n MAX_BODY_BYTES,\n widgetCustomer,\n type WidgetRoutesOptions,\n} from './routes.js';\n\nexport { MesubError } from './errors.js';\nexport type {\n WidgetPayment,\n WidgetRoutesOptions,\n WidgetSubscriptionDetail,\n WidgetSubscriptionList,\n} from './routes.js';\nexport type { CustomerOption, Denial, DenialReason, MesubAccess, PlanOption } from './guard.js';\nexport type { Asked } from './customer.js';\nexport type { Customer } from './answer.js';\n\nexport interface WithMesubOptions {\n /**\n * A slug, a list of which any one will do (`['pro', 'team']`), or either\n * worked out per request. `mesub.plan` says which one let it through.\n */\n plan: PlanOption<Request>;\n /** Defaults to one client built from MESUB_API_KEY. */\n client?: Mesub;\n /**\n * Who is asking, from your own auth:\n * `(req) => ({ external_id: session.userId })`, a wallet or an email. Null when\n * nobody is signed in. It must come from a session you verified, never\n * from the request itself.\n */\n customer: CustomerOption<Request>;\n /**\n * Answer a refusal yourself: a redirect, a page, your own JSON. It may be\n * async; a throw or a rejection is thrown, for Next to answer 500.\n */\n onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;\n}\n\n/** Your route handler, with who is asking and Mesub's answer as the second argument. */\nexport type MesubRouteHandler<Context = unknown> = (\n request: Request,\n mesub: MesubAccess,\n context: Context,\n) => Response | Promise<Response>;\n\n/**\n * Wraps an App Router route handler so it runs only for a subscriber with\n * access to `plan`. Next's `context` (with `params`) is passed through as is.\n *\n * Who is asking is who `customer` says, from your own auth. Refusals answer\n * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub\n * unreachable, with no answer known for that subscriber). Integration errors\n * are thrown, for Next to log and answer 500. Not for `middleware.ts`, server\n * components or pages.\n */\nexport function withMesub<Context = unknown>(\n handler: MesubRouteHandler<Context>,\n options: WithMesubOptions,\n): (request: Request, context: Context) => Promise<Response> {\n checkPlan(options.plan);\n checkCustomer(options);\n\n return async (request, context) => {\n const client = options.client ?? defaultClient();\n const outcome = await guard(client, await askerOf(request, options.customer), () =>\n plansOf(options.plan, request),\n );\n\n if (outcome.allowed) return handler(request, accessOf(outcome), context);\n\n const denial = denialOf(outcome);\n\n if (options.onDenied) return await options.onDenied(denial, request);\n\n const headers: Record<string, string> =\n outcome.reason === 'unavailable' ? { 'Retry-After': retryAfterOf(outcome) } : {};\n\n return Response.json(denialBody(outcome), { status: denial.status, headers });\n };\n}\n\n/** Next's `context` of a catch-all route: `params` is a promise since Next 15. */\ninterface CatchAllContext {\n params?: Promise<Record<string, unknown>> | Record<string, unknown>;\n}\n\n/** The path after the mount point, from the catch-all segment: `/subscriptions/sub_1/cancel`. */\nasync function pathOf(context: CatchAllContext | undefined): Promise<string> {\n const params = (await context?.params) ?? {};\n const segments = Object.values(params).find((value) => Array.isArray(value)) as\n unknown[] | undefined;\n\n return `/${(segments ?? []).map((segment) => encodeURIComponent(String(segment))).join('/')}`;\n}\n\n/**\n * The body as text, read up to the limit and no further. Null past it, by\n * `Content-Length` before anything is read, else by the bytes counted.\n */\nasync function textOf(request: Request): Promise<string | null> {\n if (Number(request.headers.get('content-length')) > MAX_BODY_BYTES) return null;\n if (request.body === null) return '';\n\n const reader = request.body.getReader();\n const decoder = new TextDecoder();\n let text = '';\n let size = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) return text + decoder.decode();\n\n size += value.byteLength;\n if (size > MAX_BODY_BYTES) {\n await reader.cancel();\n return null;\n }\n text += decoder.decode(value, { stream: true });\n }\n}\n\n/**\n * The routes `@mesub/react` calls, as an App Router catch-all:\n *\n * // app/api/mesub/[...mesub]/route.ts\n * export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });\n *\n * `customer` says who is asking from your own verified session. Integration\n * errors are thrown, for Next to log and answer 500.\n */\nexport function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {\n GET: (request: Request, context?: CatchAllContext) => Promise<Response>;\n POST: (request: Request, context?: CatchAllContext) => Promise<Response>;\n} {\n checkWidgetOptions(options);\n\n const handle = async (request: Request, context?: CatchAllContext): Promise<Response> => {\n const path = await pathOf(context);\n let body: unknown;\n\n if (request.method !== 'GET') {\n const text = await textOf(request);\n\n if (text === null) {\n return Response.json(\n { error: { code: 'payload_too_large', message: 'The body is too large.' } },\n { status: 413 },\n );\n }\n try {\n body = text === '' ? undefined : (JSON.parse(text) as unknown);\n } catch {\n return Response.json(\n { error: { code: 'invalid_request', message: 'The body is not JSON.' } },\n { status: 400 },\n );\n }\n }\n\n // A plan is public: nobody needs to be signed in to read a price.\n const publicRead = request.method === 'GET' && path.startsWith('/plans/');\n const asked = publicRead ? null : await widgetCustomer(request, options.customer);\n const email = asked && options.email ? await options.email(request) : undefined;\n const answer = await handleWidget(\n options.client ?? defaultClient(),\n {\n method: request.method,\n path,\n body,\n contentType: request.headers.get('content-type'),\n },\n asked,\n { email, ...(options.plans && { plans: options.plans }) },\n );\n\n return Response.json(answer.body, {\n status: answer.status,\n // Per customer, and it moves: never a shared cache's to keep.\n headers: { 'Cache-Control': 'no-store', ...answer.headers },\n });\n };\n\n return { GET: handle, POST: handle };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { C as Customer, j as ServerSubscription } from './subscriptions-D1hE7MUc.js';
|
|
2
|
+
import { M as Mesub } from './client-BQuRYSdB.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The routes `@mesub/react` calls on your own server, so the browser never
|
|
6
|
+
* talks to Mesub nor holds a key: a plan to show, the customer's
|
|
7
|
+
* subscriptions, one of them with its payments, and each step of
|
|
8
|
+
* subscribing (a fresh transaction to sign included), cancelling, resuming,
|
|
9
|
+
* closing and paying a late payment now. One function, `handleWidget`; the
|
|
10
|
+
* Express and Next entries only carry a request to it.
|
|
11
|
+
*
|
|
12
|
+
* Who is asking comes from `customer`, your own verified auth, as on the
|
|
13
|
+
* guards. A subscription that is not that customer's is answered 404, the
|
|
14
|
+
* same as one that does not exist.
|
|
15
|
+
*/
|
|
16
|
+
/** Who is asking, from your own auth. Null when nobody is signed in, a 401. */
|
|
17
|
+
type WidgetCustomer<Req> = (request: Req) => Customer | string | null | undefined | Promise<Customer | string | null | undefined>;
|
|
18
|
+
interface WidgetRoutesOptions<Req> {
|
|
19
|
+
/** Defaults to one client built from MESUB_API_KEY. */
|
|
20
|
+
client?: Mesub;
|
|
21
|
+
/**
|
|
22
|
+
* Who is asking, from a session you verified, never from the request
|
|
23
|
+
* itself: `(req) => ({ external_id: req.user.id })`, a wallet or an email.
|
|
24
|
+
* Every subscription created is tied to it, and only its own are reached.
|
|
25
|
+
*/
|
|
26
|
+
customer: WidgetCustomer<Req>;
|
|
27
|
+
/**
|
|
28
|
+
* Where that customer's notices go, when `customer` is not an email:
|
|
29
|
+
* `(req) => req.user.email`. Not verified by Mesub.
|
|
30
|
+
*/
|
|
31
|
+
email?: (request: Req) => string | null | undefined | Promise<string | null | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* The plans the widget may subscribe to. Any plan of your project by
|
|
34
|
+
* default; name them to keep one off your site.
|
|
35
|
+
*/
|
|
36
|
+
plans?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
/** What `GET /subscriptions` answers. */
|
|
39
|
+
interface WidgetSubscriptionList {
|
|
40
|
+
/** Newest first, expired checkouts included: the 500 newest at most. */
|
|
41
|
+
subscriptions: Array<Omit<ServerSubscription, 'email' | 'external_id'>>;
|
|
42
|
+
/** True when the customer has more than those, which are not read. */
|
|
43
|
+
has_more: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** One pull attempt as the browser gets it: the fields Mesub serves, and no other. */
|
|
46
|
+
interface WidgetPayment {
|
|
47
|
+
attempted_at: string;
|
|
48
|
+
/** `paid`, `skipped`, `rejected` or `blocked` today; a newer one is handed on as is. */
|
|
49
|
+
outcome: string;
|
|
50
|
+
/** What was asked for, in the mint's smallest unit; on a paid one, what was paid. */
|
|
51
|
+
amount: string;
|
|
52
|
+
/**
|
|
53
|
+
* That amount in dollars when it was attempted, a decimal string. Null
|
|
54
|
+
* until Mesub priced it, and from a Mesub that does not say.
|
|
55
|
+
*/
|
|
56
|
+
amount_usd: string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Mesub's short reason, null on a paid one, as on `ServedAttempt`: snake
|
|
59
|
+
* case, an open string with the `program_${number}` and
|
|
60
|
+
* `solana_${number}` families.
|
|
61
|
+
*/
|
|
62
|
+
reason: string | null;
|
|
63
|
+
/** The transaction that paid. Null on every outcome but `paid`. */
|
|
64
|
+
signature: string | null;
|
|
65
|
+
/** A retry of a missed charge. Null from a Mesub that does not say. */
|
|
66
|
+
retry: boolean | null;
|
|
67
|
+
/**
|
|
68
|
+
* Which retry and out of how many, as Mesub recorded them when it ran:
|
|
69
|
+
* 2 and 3 for "retry 2 / 3". Null when it is no retry of the plan's, or
|
|
70
|
+
* Mesub does not say.
|
|
71
|
+
*/
|
|
72
|
+
retry_number: number | null;
|
|
73
|
+
retries_allowed: number | null;
|
|
74
|
+
/** The start of the period it was for. Null when Mesub recorded none or does not say. */
|
|
75
|
+
period_start: string | null;
|
|
76
|
+
}
|
|
77
|
+
/** What `GET /subscriptions/:id` answers. */
|
|
78
|
+
interface WidgetSubscriptionDetail {
|
|
79
|
+
subscription: Omit<ServerSubscription, 'email' | 'external_id'>;
|
|
80
|
+
/** What Mesub pulls next, soonest first: one entry at most, none when nothing is due. */
|
|
81
|
+
upcoming: WidgetUpcoming[];
|
|
82
|
+
/**
|
|
83
|
+
* This subscription's own pull attempts, newest first: the first page,
|
|
84
|
+
* twenty at most, so not always its full history. Null when they could
|
|
85
|
+
* not be read: `payments_error` says why.
|
|
86
|
+
*/
|
|
87
|
+
payments: WidgetPayment[] | null;
|
|
88
|
+
/**
|
|
89
|
+
* What it paid since it began, counted by Mesub over all its attempts,
|
|
90
|
+
* not over `payments`: how many were paid, and their sum in the mint's
|
|
91
|
+
* smallest unit. Null when the read failed, and from a Mesub that does
|
|
92
|
+
* not serve it: never summed here.
|
|
93
|
+
*/
|
|
94
|
+
paid: {
|
|
95
|
+
count: number;
|
|
96
|
+
amount: string;
|
|
97
|
+
} | null;
|
|
98
|
+
/** Why `payments` is null; null otherwise. */
|
|
99
|
+
payments_error: {
|
|
100
|
+
code: string;
|
|
101
|
+
message: string;
|
|
102
|
+
} | null;
|
|
103
|
+
}
|
|
104
|
+
/** The one pull Mesub announces next for a subscription. */
|
|
105
|
+
interface WidgetUpcoming {
|
|
106
|
+
/** `charge` at the due date of a running one, `retry` of a missed one. */
|
|
107
|
+
kind: 'charge' | 'retry';
|
|
108
|
+
due_at: string;
|
|
109
|
+
/**
|
|
110
|
+
* The plan's price in the mint's smallest unit, and as a person counts
|
|
111
|
+
* it. Null when the plan could not be read, never guessed.
|
|
112
|
+
*/
|
|
113
|
+
amount: string | null;
|
|
114
|
+
amount_display: string | null;
|
|
115
|
+
/**
|
|
116
|
+
* The plan's amount in dollars at the latest price Mesub knows, a decimal
|
|
117
|
+
* string such as `"9.99"`: an estimate to show beside the amount, never
|
|
118
|
+
* what is charged. Null when Mesub knows no price, and when the plan could
|
|
119
|
+
* not be read.
|
|
120
|
+
*/
|
|
121
|
+
amount_usd: string | null;
|
|
122
|
+
/**
|
|
123
|
+
* On a `retry`, which one it will be and out of how many: 2 and 3 for
|
|
124
|
+
* "next try 2 / 3". Null on a `charge`, and when Mesub does not say.
|
|
125
|
+
*/
|
|
126
|
+
retry_number: number | null;
|
|
127
|
+
retries_allowed: number | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type { WidgetRoutesOptions as W, WidgetPayment as a, WidgetSubscriptionDetail as b, WidgetSubscriptionList as c };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { C as Customer, j as ServerSubscription } from './subscriptions-D1hE7MUc.cjs';
|
|
2
|
+
import { M as Mesub } from './client-Dm1jamju.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The routes `@mesub/react` calls on your own server, so the browser never
|
|
6
|
+
* talks to Mesub nor holds a key: a plan to show, the customer's
|
|
7
|
+
* subscriptions, one of them with its payments, and each step of
|
|
8
|
+
* subscribing (a fresh transaction to sign included), cancelling, resuming,
|
|
9
|
+
* closing and paying a late payment now. One function, `handleWidget`; the
|
|
10
|
+
* Express and Next entries only carry a request to it.
|
|
11
|
+
*
|
|
12
|
+
* Who is asking comes from `customer`, your own verified auth, as on the
|
|
13
|
+
* guards. A subscription that is not that customer's is answered 404, the
|
|
14
|
+
* same as one that does not exist.
|
|
15
|
+
*/
|
|
16
|
+
/** Who is asking, from your own auth. Null when nobody is signed in, a 401. */
|
|
17
|
+
type WidgetCustomer<Req> = (request: Req) => Customer | string | null | undefined | Promise<Customer | string | null | undefined>;
|
|
18
|
+
interface WidgetRoutesOptions<Req> {
|
|
19
|
+
/** Defaults to one client built from MESUB_API_KEY. */
|
|
20
|
+
client?: Mesub;
|
|
21
|
+
/**
|
|
22
|
+
* Who is asking, from a session you verified, never from the request
|
|
23
|
+
* itself: `(req) => ({ external_id: req.user.id })`, a wallet or an email.
|
|
24
|
+
* Every subscription created is tied to it, and only its own are reached.
|
|
25
|
+
*/
|
|
26
|
+
customer: WidgetCustomer<Req>;
|
|
27
|
+
/**
|
|
28
|
+
* Where that customer's notices go, when `customer` is not an email:
|
|
29
|
+
* `(req) => req.user.email`. Not verified by Mesub.
|
|
30
|
+
*/
|
|
31
|
+
email?: (request: Req) => string | null | undefined | Promise<string | null | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* The plans the widget may subscribe to. Any plan of your project by
|
|
34
|
+
* default; name them to keep one off your site.
|
|
35
|
+
*/
|
|
36
|
+
plans?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
/** What `GET /subscriptions` answers. */
|
|
39
|
+
interface WidgetSubscriptionList {
|
|
40
|
+
/** Newest first, expired checkouts included: the 500 newest at most. */
|
|
41
|
+
subscriptions: Array<Omit<ServerSubscription, 'email' | 'external_id'>>;
|
|
42
|
+
/** True when the customer has more than those, which are not read. */
|
|
43
|
+
has_more: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** One pull attempt as the browser gets it: the fields Mesub serves, and no other. */
|
|
46
|
+
interface WidgetPayment {
|
|
47
|
+
attempted_at: string;
|
|
48
|
+
/** `paid`, `skipped`, `rejected` or `blocked` today; a newer one is handed on as is. */
|
|
49
|
+
outcome: string;
|
|
50
|
+
/** What was asked for, in the mint's smallest unit; on a paid one, what was paid. */
|
|
51
|
+
amount: string;
|
|
52
|
+
/**
|
|
53
|
+
* That amount in dollars when it was attempted, a decimal string. Null
|
|
54
|
+
* until Mesub priced it, and from a Mesub that does not say.
|
|
55
|
+
*/
|
|
56
|
+
amount_usd: string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Mesub's short reason, null on a paid one, as on `ServedAttempt`: snake
|
|
59
|
+
* case, an open string with the `program_${number}` and
|
|
60
|
+
* `solana_${number}` families.
|
|
61
|
+
*/
|
|
62
|
+
reason: string | null;
|
|
63
|
+
/** The transaction that paid. Null on every outcome but `paid`. */
|
|
64
|
+
signature: string | null;
|
|
65
|
+
/** A retry of a missed charge. Null from a Mesub that does not say. */
|
|
66
|
+
retry: boolean | null;
|
|
67
|
+
/**
|
|
68
|
+
* Which retry and out of how many, as Mesub recorded them when it ran:
|
|
69
|
+
* 2 and 3 for "retry 2 / 3". Null when it is no retry of the plan's, or
|
|
70
|
+
* Mesub does not say.
|
|
71
|
+
*/
|
|
72
|
+
retry_number: number | null;
|
|
73
|
+
retries_allowed: number | null;
|
|
74
|
+
/** The start of the period it was for. Null when Mesub recorded none or does not say. */
|
|
75
|
+
period_start: string | null;
|
|
76
|
+
}
|
|
77
|
+
/** What `GET /subscriptions/:id` answers. */
|
|
78
|
+
interface WidgetSubscriptionDetail {
|
|
79
|
+
subscription: Omit<ServerSubscription, 'email' | 'external_id'>;
|
|
80
|
+
/** What Mesub pulls next, soonest first: one entry at most, none when nothing is due. */
|
|
81
|
+
upcoming: WidgetUpcoming[];
|
|
82
|
+
/**
|
|
83
|
+
* This subscription's own pull attempts, newest first: the first page,
|
|
84
|
+
* twenty at most, so not always its full history. Null when they could
|
|
85
|
+
* not be read: `payments_error` says why.
|
|
86
|
+
*/
|
|
87
|
+
payments: WidgetPayment[] | null;
|
|
88
|
+
/**
|
|
89
|
+
* What it paid since it began, counted by Mesub over all its attempts,
|
|
90
|
+
* not over `payments`: how many were paid, and their sum in the mint's
|
|
91
|
+
* smallest unit. Null when the read failed, and from a Mesub that does
|
|
92
|
+
* not serve it: never summed here.
|
|
93
|
+
*/
|
|
94
|
+
paid: {
|
|
95
|
+
count: number;
|
|
96
|
+
amount: string;
|
|
97
|
+
} | null;
|
|
98
|
+
/** Why `payments` is null; null otherwise. */
|
|
99
|
+
payments_error: {
|
|
100
|
+
code: string;
|
|
101
|
+
message: string;
|
|
102
|
+
} | null;
|
|
103
|
+
}
|
|
104
|
+
/** The one pull Mesub announces next for a subscription. */
|
|
105
|
+
interface WidgetUpcoming {
|
|
106
|
+
/** `charge` at the due date of a running one, `retry` of a missed one. */
|
|
107
|
+
kind: 'charge' | 'retry';
|
|
108
|
+
due_at: string;
|
|
109
|
+
/**
|
|
110
|
+
* The plan's price in the mint's smallest unit, and as a person counts
|
|
111
|
+
* it. Null when the plan could not be read, never guessed.
|
|
112
|
+
*/
|
|
113
|
+
amount: string | null;
|
|
114
|
+
amount_display: string | null;
|
|
115
|
+
/**
|
|
116
|
+
* The plan's amount in dollars at the latest price Mesub knows, a decimal
|
|
117
|
+
* string such as `"9.99"`: an estimate to show beside the amount, never
|
|
118
|
+
* what is charged. Null when Mesub knows no price, and when the plan could
|
|
119
|
+
* not be read.
|
|
120
|
+
*/
|
|
121
|
+
amount_usd: string | null;
|
|
122
|
+
/**
|
|
123
|
+
* On a `retry`, which one it will be and out of how many: 2 and 3 for
|
|
124
|
+
* "next try 2 / 3". Null on a `charge`, and when Mesub does not say.
|
|
125
|
+
*/
|
|
126
|
+
retry_number: number | null;
|
|
127
|
+
retries_allowed: number | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type { WidgetRoutesOptions as W, WidgetPayment as a, WidgetSubscriptionDetail as b, WidgetSubscriptionList as c };
|