@onesub/server 0.14.0 → 0.16.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/dist/__tests__/google-subscriptions-v2.test.d.ts +1 -1
- package/dist/__tests__/receipt-age.test.d.ts +7 -0
- package/dist/__tests__/receipt-age.test.d.ts.map +1 -0
- package/dist/__tests__/tracing.test.d.ts +2 -0
- package/dist/__tests__/tracing.test.d.ts.map +1 -0
- package/dist/cache.d.ts +5 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/index.cjs +178 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +178 -55
- package/dist/index.js.map +1 -1
- package/dist/providers/apple.d.ts.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/mock.d.ts.map +1 -1
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/purchase.d.ts.map +1 -1
- package/dist/routes/validate.d.ts.map +1 -1
- package/dist/routes/webhook-apple.d.ts.map +1 -1
- package/dist/routes/webhook-google.d.ts +1 -1
- package/dist/routes/webhook-google.d.ts.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/stores/postgres.d.ts.map +1 -1
- package/dist/stores/redis.d.ts +1 -0
- package/dist/stores/redis.d.ts.map +1 -1
- package/dist/tracing.d.ts.map +1 -1
- package/dist/webhook-events.d.ts +10 -0
- package/dist/webhook-events.d.ts.map +1 -1
- package/package.json +5 -3
- package/dist/__tests__/admin-customer-detail.test.js +0 -146
- package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
- package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
- package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
- package/dist/__tests__/admin-subscription-detail.test.js +0 -112
- package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
- package/dist/__tests__/apple-root-ca.test.js +0 -41
- package/dist/__tests__/apple-root-ca.test.js.map +0 -1
- package/dist/__tests__/apple-status-api.test.js +0 -345
- package/dist/__tests__/apple-status-api.test.js.map +0 -1
- package/dist/__tests__/cache.test.js +0 -34
- package/dist/__tests__/cache.test.js.map +0 -1
- package/dist/__tests__/entitlements.test.js +0 -268
- package/dist/__tests__/entitlements.test.js.map +0 -1
- package/dist/__tests__/error-codes.test.js +0 -173
- package/dist/__tests__/error-codes.test.js.map +0 -1
- package/dist/__tests__/fetch-hardening.test.js +0 -192
- package/dist/__tests__/fetch-hardening.test.js.map +0 -1
- package/dist/__tests__/google-paused.test.js +0 -126
- package/dist/__tests__/google-paused.test.js.map +0 -1
- package/dist/__tests__/google-price-change.test.js +0 -186
- package/dist/__tests__/google-price-change.test.js.map +0 -1
- package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
- package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
- package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
- package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
- package/dist/__tests__/lifecycle-states.test.js +0 -423
- package/dist/__tests__/lifecycle-states.test.js.map +0 -1
- package/dist/__tests__/linked-purchase-token.test.js +0 -227
- package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
- package/dist/__tests__/metrics.test.js +0 -340
- package/dist/__tests__/metrics.test.js.map +0 -1
- package/dist/__tests__/mock-provider.test.js +0 -188
- package/dist/__tests__/mock-provider.test.js.map +0 -1
- package/dist/__tests__/openapi.test.js +0 -32
- package/dist/__tests__/openapi.test.js.map +0 -1
- package/dist/__tests__/paused-auto-resume.test.js +0 -162
- package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
- package/dist/__tests__/providers.test.js +0 -321
- package/dist/__tests__/providers.test.js.map +0 -1
- package/dist/__tests__/redis-store.test.js +0 -166
- package/dist/__tests__/redis-store.test.js.map +0 -1
- package/dist/__tests__/refund-policy.test.js +0 -317
- package/dist/__tests__/refund-policy.test.js.map +0 -1
- package/dist/__tests__/schema.test.js +0 -46
- package/dist/__tests__/schema.test.js.map +0 -1
- package/dist/__tests__/store.test.js +0 -172
- package/dist/__tests__/store.test.js.map +0 -1
- package/dist/__tests__/test-utils.js +0 -30
- package/dist/__tests__/test-utils.js.map +0 -1
- package/dist/__tests__/webhook-apple.test.js +0 -247
- package/dist/__tests__/webhook-apple.test.js.map +0 -1
- package/dist/__tests__/webhook-events.test.js +0 -36
- package/dist/__tests__/webhook-events.test.js.map +0 -1
- package/dist/__tests__/webhook-google.test.js +0 -255
- package/dist/__tests__/webhook-google.test.js.map +0 -1
- package/dist/__tests__/webhook-queue.test.js +0 -26
- package/dist/__tests__/webhook-queue.test.js.map +0 -1
- package/dist/__tests__/webhook-refund.test.js +0 -313
- package/dist/__tests__/webhook-refund.test.js.map +0 -1
- package/dist/cache.js +0 -39
- package/dist/cache.js.map +0 -1
- package/dist/errors.js +0 -19
- package/dist/errors.js.map +0 -1
- package/dist/http.js +0 -36
- package/dist/http.js.map +0 -1
- package/dist/logger.js +0 -20
- package/dist/logger.js.map +0 -1
- package/dist/openapi.js +0 -196
- package/dist/openapi.js.map +0 -1
- package/dist/providers/apple-root-ca.js +0 -38
- package/dist/providers/apple-root-ca.js.map +0 -1
- package/dist/providers/apple.js +0 -630
- package/dist/providers/apple.js.map +0 -1
- package/dist/providers/google.js +0 -564
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/mock.js +0 -93
- package/dist/providers/mock.js.map +0 -1
- package/dist/routes/admin.js +0 -327
- package/dist/routes/admin.js.map +0 -1
- package/dist/routes/apple-offer.js +0 -72
- package/dist/routes/apple-offer.js.map +0 -1
- package/dist/routes/entitlements.js +0 -130
- package/dist/routes/entitlements.js.map +0 -1
- package/dist/routes/metrics.js +0 -281
- package/dist/routes/metrics.js.map +0 -1
- package/dist/routes/purchase.js +0 -204
- package/dist/routes/purchase.js.map +0 -1
- package/dist/routes/status.js +0 -55
- package/dist/routes/status.js.map +0 -1
- package/dist/routes/validate.js +0 -70
- package/dist/routes/validate.js.map +0 -1
- package/dist/routes/webhook-apple.js +0 -145
- package/dist/routes/webhook-apple.js.map +0 -1
- package/dist/routes/webhook-google.js +0 -204
- package/dist/routes/webhook-google.js.map +0 -1
- package/dist/routes/webhook.js +0 -19
- package/dist/routes/webhook.js.map +0 -1
- package/dist/store.js +0 -145
- package/dist/store.js.map +0 -1
- package/dist/stores/postgres.js +0 -329
- package/dist/stores/postgres.js.map +0 -1
- package/dist/stores/redis.js +0 -297
- package/dist/stores/redis.js.map +0 -1
- package/dist/stores/schema.js +0 -67
- package/dist/stores/schema.js.map +0 -1
- package/dist/tracing.js +0 -74
- package/dist/tracing.js.map +0 -1
- package/dist/webhook-events.js +0 -58
- package/dist/webhook-events.js.map +0 -1
- package/dist/webhook-queue.js +0 -108
- package/dist/webhook-queue.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entitlements.js","sourceRoot":"","sources":["../../src/routes/entitlements.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/metrics.js
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { ROUTES, SUBSCRIPTION_STATUS, PURCHASE_TYPE, ONESUB_ERROR_CODE, } from '@onesub/shared';
|
|
4
|
-
import { log } from '../logger.js';
|
|
5
|
-
import { sendError } from '../errors.js';
|
|
6
|
-
const ADMIN_SECRET_HEADER = 'x-admin-secret';
|
|
7
|
-
/**
|
|
8
|
-
* Read-only aggregate metrics endpoints — gated behind `config.adminSecret`.
|
|
9
|
-
* Returns count-based metrics only (active count, started count, expired count).
|
|
10
|
-
*
|
|
11
|
-
* Revenue metrics (MRR, ARR, LTV) require per-product price configuration the
|
|
12
|
-
* server doesn't currently track; deferred to a follow-up release that adds
|
|
13
|
-
* `config.products: { 'pro_monthly': { price: 9.99, currency: 'USD' } }`.
|
|
14
|
-
*
|
|
15
|
-
* Aggregation strategy: pulls every record via `store.listAll()` and reduces
|
|
16
|
-
* in memory. Fine up to ~100k records; large deployments should optimise the
|
|
17
|
-
* Postgres path with SQL aggregates (separate PR).
|
|
18
|
-
*/
|
|
19
|
-
export function createMetricsRouter(config, store, purchaseStore) {
|
|
20
|
-
if (!config.adminSecret)
|
|
21
|
-
return null;
|
|
22
|
-
const router = Router();
|
|
23
|
-
const adminSecret = config.adminSecret;
|
|
24
|
-
// Auth middleware — only protects /onesub/metrics/* (siblings unaffected
|
|
25
|
-
// even when this router is mounted on the parent root).
|
|
26
|
-
router.use('/onesub/metrics', (req, res, next) => {
|
|
27
|
-
const provided = req.headers[ADMIN_SECRET_HEADER];
|
|
28
|
-
if (typeof provided !== 'string' || provided !== adminSecret) {
|
|
29
|
-
sendError(res, 401, ONESUB_ERROR_CODE.INVALID_ADMIN_SECRET, 'INVALID_ADMIN_SECRET');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
next();
|
|
33
|
-
});
|
|
34
|
-
// ── helpers ──────────────────────────────────────────────────────────────
|
|
35
|
-
const isActiveSub = (sub, now) => {
|
|
36
|
-
const statusAllows = sub.status === SUBSCRIPTION_STATUS.ACTIVE ||
|
|
37
|
-
sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD;
|
|
38
|
-
return statusAllows && new Date(sub.expiresAt).getTime() > now;
|
|
39
|
-
};
|
|
40
|
-
const bump = (map, key) => {
|
|
41
|
-
map[key] = (map[key] ?? 0) + 1;
|
|
42
|
-
};
|
|
43
|
-
// ── GET /onesub/metrics/active ───────────────────────────────────────────
|
|
44
|
-
router.get(ROUTES.METRICS_ACTIVE, async (_req, res) => {
|
|
45
|
-
try {
|
|
46
|
-
const [subs, purchases] = await Promise.all([
|
|
47
|
-
store.listAll(),
|
|
48
|
-
purchaseStore.listAll(),
|
|
49
|
-
]);
|
|
50
|
-
const now = Date.now();
|
|
51
|
-
const byProduct = {};
|
|
52
|
-
const byProductPurchases = {};
|
|
53
|
-
const byPlatform = {};
|
|
54
|
-
let activeSubscriptions = 0;
|
|
55
|
-
let gracePeriodSubscriptions = 0;
|
|
56
|
-
let nonConsumablePurchases = 0;
|
|
57
|
-
for (const sub of subs) {
|
|
58
|
-
if (!isActiveSub(sub, now))
|
|
59
|
-
continue;
|
|
60
|
-
activeSubscriptions++;
|
|
61
|
-
if (sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD) {
|
|
62
|
-
gracePeriodSubscriptions++;
|
|
63
|
-
}
|
|
64
|
-
bump(byProduct, sub.productId);
|
|
65
|
-
bump(byPlatform, sub.platform);
|
|
66
|
-
}
|
|
67
|
-
for (const p of purchases) {
|
|
68
|
-
if (p.type !== PURCHASE_TYPE.NON_CONSUMABLE)
|
|
69
|
-
continue;
|
|
70
|
-
nonConsumablePurchases++;
|
|
71
|
-
// byProductPurchases is purchase-only; byProduct (subs-only) stays clean
|
|
72
|
-
// so dashboards can render distinct "subscription mix" vs "lifetime mix"
|
|
73
|
-
// panels without untangling them client-side.
|
|
74
|
-
bump(byProductPurchases, p.productId);
|
|
75
|
-
bump(byPlatform, p.platform);
|
|
76
|
-
}
|
|
77
|
-
const response = {
|
|
78
|
-
total: activeSubscriptions + nonConsumablePurchases,
|
|
79
|
-
activeSubscriptions,
|
|
80
|
-
gracePeriodSubscriptions,
|
|
81
|
-
nonConsumablePurchases,
|
|
82
|
-
byProduct,
|
|
83
|
-
byProductPurchases,
|
|
84
|
-
byPlatform,
|
|
85
|
-
};
|
|
86
|
-
res.status(200).json(response);
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
log.error('[onesub/metrics/active] error:', err);
|
|
90
|
-
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error');
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
// ── GET /onesub/metrics/started?from=&to=&groupBy= ───────────────────────
|
|
94
|
-
const rangeSchema = z.object({
|
|
95
|
-
from: z.string().min(1),
|
|
96
|
-
to: z.string().min(1),
|
|
97
|
-
groupBy: z.enum(['none', 'day']).optional(),
|
|
98
|
-
});
|
|
99
|
-
function parseRange(req) {
|
|
100
|
-
const parsed = rangeSchema.safeParse(req.query);
|
|
101
|
-
if (!parsed.success)
|
|
102
|
-
return { error: 'from and to are required (ISO 8601)' };
|
|
103
|
-
const fromMs = new Date(parsed.data.from).getTime();
|
|
104
|
-
const toMs = new Date(parsed.data.to).getTime();
|
|
105
|
-
if (Number.isNaN(fromMs) || Number.isNaN(toMs)) {
|
|
106
|
-
return { error: 'from / to must be ISO 8601 timestamps' };
|
|
107
|
-
}
|
|
108
|
-
if (fromMs > toMs)
|
|
109
|
-
return { error: 'from must be ≤ to' };
|
|
110
|
-
return { fromMs, toMs, groupBy: parsed.data.groupBy ?? 'none' };
|
|
111
|
-
}
|
|
112
|
-
// UTC `YYYY-MM-DD` for a given epoch-ms. Used to assign each record to a
|
|
113
|
-
// calendar-day bucket; UTC keeps the result deterministic regardless of
|
|
114
|
-
// server timezone (Postgres `updated_at` is UTC; SubscriptionInfo dates are
|
|
115
|
-
// ISO with explicit zone offsets).
|
|
116
|
-
function utcDateKey(ms) {
|
|
117
|
-
const d = new Date(ms);
|
|
118
|
-
const yyyy = d.getUTCFullYear();
|
|
119
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
120
|
-
const dd = String(d.getUTCDate()).padStart(2, '0');
|
|
121
|
-
return `${yyyy}-${mm}-${dd}`;
|
|
122
|
-
}
|
|
123
|
-
// Zero-fill a daily series across [fromMs, toMs] (inclusive of both
|
|
124
|
-
// boundary days). The route handler then increments the counts.
|
|
125
|
-
function emptyDailyBuckets(fromMs, toMs) {
|
|
126
|
-
const out = [];
|
|
127
|
-
// Snap `from` to UTC midnight so iteration steps land on calendar boundaries.
|
|
128
|
-
const start = new Date(fromMs);
|
|
129
|
-
start.setUTCHours(0, 0, 0, 0);
|
|
130
|
-
let cur = start.getTime();
|
|
131
|
-
while (cur <= toMs) {
|
|
132
|
-
out.push({ date: utcDateKey(cur), count: 0 });
|
|
133
|
-
cur += 86_400_000;
|
|
134
|
-
}
|
|
135
|
-
return out;
|
|
136
|
-
}
|
|
137
|
-
router.get(ROUTES.METRICS_STARTED, async (req, res) => {
|
|
138
|
-
const range = parseRange(req);
|
|
139
|
-
if ('error' in range) {
|
|
140
|
-
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, range.error);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
const subs = await store.listAll();
|
|
145
|
-
const byProduct = {};
|
|
146
|
-
const byPlatform = {};
|
|
147
|
-
let total = 0;
|
|
148
|
-
// Build a date→index map only when bucketing is requested — keeps the
|
|
149
|
-
// non-bucketed path identical to the previous implementation.
|
|
150
|
-
const buckets = range.groupBy === 'day' ? emptyDailyBuckets(range.fromMs, range.toMs) : null;
|
|
151
|
-
const bucketIndex = buckets ? new Map(buckets.map((b, i) => [b.date, i])) : null;
|
|
152
|
-
for (const sub of subs) {
|
|
153
|
-
const purchasedMs = new Date(sub.purchasedAt).getTime();
|
|
154
|
-
if (purchasedMs < range.fromMs || purchasedMs > range.toMs)
|
|
155
|
-
continue;
|
|
156
|
-
total++;
|
|
157
|
-
bump(byProduct, sub.productId);
|
|
158
|
-
bump(byPlatform, sub.platform);
|
|
159
|
-
if (buckets && bucketIndex) {
|
|
160
|
-
const idx = bucketIndex.get(utcDateKey(purchasedMs));
|
|
161
|
-
if (idx !== undefined)
|
|
162
|
-
buckets[idx].count++;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
const response = {
|
|
166
|
-
from: req.query['from'],
|
|
167
|
-
to: req.query['to'],
|
|
168
|
-
total,
|
|
169
|
-
byProduct,
|
|
170
|
-
byPlatform,
|
|
171
|
-
...(buckets ? { buckets } : {}),
|
|
172
|
-
};
|
|
173
|
-
res.status(200).json(response);
|
|
174
|
-
}
|
|
175
|
-
catch (err) {
|
|
176
|
-
log.error('[onesub/metrics/started] error:', err);
|
|
177
|
-
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error');
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
// ── GET /onesub/metrics/expired?from=&to= ────────────────────────────────
|
|
181
|
-
router.get(ROUTES.METRICS_EXPIRED, async (req, res) => {
|
|
182
|
-
const range = parseRange(req);
|
|
183
|
-
if ('error' in range) {
|
|
184
|
-
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, range.error);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
const subs = await store.listAll();
|
|
189
|
-
const byProduct = {};
|
|
190
|
-
const byPlatform = {};
|
|
191
|
-
let total = 0;
|
|
192
|
-
const buckets = range.groupBy === 'day' ? emptyDailyBuckets(range.fromMs, range.toMs) : null;
|
|
193
|
-
const bucketIndex = buckets ? new Map(buckets.map((b, i) => [b.date, i])) : null;
|
|
194
|
-
for (const sub of subs) {
|
|
195
|
-
// Counted only if currently expired or canceled — a record that's
|
|
196
|
-
// still active doesn't qualify even if its expiresAt happened to fall
|
|
197
|
-
// inside the window (e.g. mid-period billing renewal).
|
|
198
|
-
if (sub.status !== SUBSCRIPTION_STATUS.EXPIRED &&
|
|
199
|
-
sub.status !== SUBSCRIPTION_STATUS.CANCELED)
|
|
200
|
-
continue;
|
|
201
|
-
const expiredMs = new Date(sub.expiresAt).getTime();
|
|
202
|
-
if (expiredMs < range.fromMs || expiredMs > range.toMs)
|
|
203
|
-
continue;
|
|
204
|
-
total++;
|
|
205
|
-
bump(byProduct, sub.productId);
|
|
206
|
-
bump(byPlatform, sub.platform);
|
|
207
|
-
if (buckets && bucketIndex) {
|
|
208
|
-
const idx = bucketIndex.get(utcDateKey(expiredMs));
|
|
209
|
-
if (idx !== undefined)
|
|
210
|
-
buckets[idx].count++;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const response = {
|
|
214
|
-
from: req.query['from'],
|
|
215
|
-
to: req.query['to'],
|
|
216
|
-
total,
|
|
217
|
-
byProduct,
|
|
218
|
-
byPlatform,
|
|
219
|
-
...(buckets ? { buckets } : {}),
|
|
220
|
-
};
|
|
221
|
-
res.status(200).json(response);
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
log.error('[onesub/metrics/expired] error:', err);
|
|
225
|
-
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error');
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
// ── GET /onesub/metrics/purchases/started?from=&to=&groupBy= ─────────────
|
|
229
|
-
// Counts non-consumable purchases (lifetime products) by purchasedAt within
|
|
230
|
-
// the window. Backs the dashboard's Purchases timeseries chart so hosts that
|
|
231
|
-
// sell only lifetime products (e.g. coffee's Welcome Pass) get a meaningful
|
|
232
|
-
// growth signal even when they have no subscription data.
|
|
233
|
-
//
|
|
234
|
-
// Consumables are excluded — they grant a one-time resource (coins, lives),
|
|
235
|
-
// not an ongoing entitlement, and would dominate the count noisily.
|
|
236
|
-
router.get(ROUTES.METRICS_PURCHASES_STARTED, async (req, res) => {
|
|
237
|
-
const range = parseRange(req);
|
|
238
|
-
if ('error' in range) {
|
|
239
|
-
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, range.error);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
try {
|
|
243
|
-
const purchases = await purchaseStore.listAll();
|
|
244
|
-
const byProduct = {};
|
|
245
|
-
const byPlatform = {};
|
|
246
|
-
let total = 0;
|
|
247
|
-
const buckets = range.groupBy === 'day' ? emptyDailyBuckets(range.fromMs, range.toMs) : null;
|
|
248
|
-
const bucketIndex = buckets ? new Map(buckets.map((b, i) => [b.date, i])) : null;
|
|
249
|
-
for (const p of purchases) {
|
|
250
|
-
if (p.type !== PURCHASE_TYPE.NON_CONSUMABLE)
|
|
251
|
-
continue;
|
|
252
|
-
const purchasedMs = new Date(p.purchasedAt).getTime();
|
|
253
|
-
if (purchasedMs < range.fromMs || purchasedMs > range.toMs)
|
|
254
|
-
continue;
|
|
255
|
-
total++;
|
|
256
|
-
bump(byProduct, p.productId);
|
|
257
|
-
bump(byPlatform, p.platform);
|
|
258
|
-
if (buckets && bucketIndex) {
|
|
259
|
-
const idx = bucketIndex.get(utcDateKey(purchasedMs));
|
|
260
|
-
if (idx !== undefined)
|
|
261
|
-
buckets[idx].count++;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const response = {
|
|
265
|
-
from: req.query['from'],
|
|
266
|
-
to: req.query['to'],
|
|
267
|
-
total,
|
|
268
|
-
byProduct,
|
|
269
|
-
byPlatform,
|
|
270
|
-
...(buckets ? { buckets } : {}),
|
|
271
|
-
};
|
|
272
|
-
res.status(200).json(response);
|
|
273
|
-
}
|
|
274
|
-
catch (err) {
|
|
275
|
-
log.error('[onesub/metrics/purchases/started] error:', err);
|
|
276
|
-
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error');
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
return router;
|
|
280
|
-
}
|
|
281
|
-
//# sourceMappingURL=metrics.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/routes/metrics.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/purchase.js
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { ROUTES, PURCHASE_TYPE, ONESUB_ERROR_CODE } from '@onesub/shared';
|
|
4
|
-
import { validateAppleConsumableReceipt } from '../providers/apple.js';
|
|
5
|
-
import { validateGoogleProductReceipt, consumeGoogleProductReceipt, acknowledgeGoogleProduct, } from '../providers/google.js';
|
|
6
|
-
import { log } from '../logger.js';
|
|
7
|
-
import { sendError, sendZodError } from '../errors.js';
|
|
8
|
-
const NO_PURCHASE = { valid: false, purchase: null };
|
|
9
|
-
const validatePurchaseSchema = z.object({
|
|
10
|
-
platform: z.enum(['apple', 'google']),
|
|
11
|
-
receipt: z.string().min(1).max(10000),
|
|
12
|
-
userId: z.string().min(1).max(256),
|
|
13
|
-
productId: z.string().min(1).max(256),
|
|
14
|
-
type: z.enum([PURCHASE_TYPE.CONSUMABLE, PURCHASE_TYPE.NON_CONSUMABLE]),
|
|
15
|
-
});
|
|
16
|
-
const purchaseStatusQuerySchema = z.object({
|
|
17
|
-
userId: z.string().min(1).max(256),
|
|
18
|
-
productId: z.string().min(1).max(256).optional(),
|
|
19
|
-
});
|
|
20
|
-
export function createPurchaseRouter(config, purchaseStore) {
|
|
21
|
-
const router = Router();
|
|
22
|
-
/**
|
|
23
|
-
* POST /onesub/purchase/validate
|
|
24
|
-
*
|
|
25
|
-
* Validate a receipt for a consumable or non-consumable purchase.
|
|
26
|
-
* - Non-consumables: rejected if the user already owns the product.
|
|
27
|
-
* - Consumables: always recorded (multiple purchases allowed).
|
|
28
|
-
*
|
|
29
|
-
* Uses platform-specific product validators (purchases.products API) instead
|
|
30
|
-
* of the subscription validator. Key differences:
|
|
31
|
-
* - Apple: validates tx.type === 'Consumable'|'Non-Consumable', uses transactionId
|
|
32
|
-
* (not originalTransactionId) as the dedup key, enforces 72h receipt age
|
|
33
|
-
* - Google: checks consumptionState for consumables (replay prevention),
|
|
34
|
-
* enforces 72h receipt age, uses orderId as the dedup key
|
|
35
|
-
*/
|
|
36
|
-
router.post(ROUTES.VALIDATE_PURCHASE, async (req, res) => {
|
|
37
|
-
let body;
|
|
38
|
-
try {
|
|
39
|
-
body = validatePurchaseSchema.parse(req.body);
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
if (err instanceof z.ZodError) {
|
|
43
|
-
sendZodError(res, err, NO_PURCHASE);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
throw err;
|
|
47
|
-
}
|
|
48
|
-
const { platform, receipt, userId, productId, type } = body;
|
|
49
|
-
try {
|
|
50
|
-
// Non-consumable idempotent restore. If the user already owns this
|
|
51
|
-
// product, return the *recorded* purchase — with its real transactionId —
|
|
52
|
-
// as a `restored` success instead of a 409 error.
|
|
53
|
-
//
|
|
54
|
-
// Owning a non-consumable IS success. The old behaviour (409 +
|
|
55
|
-
// `purchase: null`) forced the SDK to synthesize a restored result with
|
|
56
|
-
// no transactionId, so downstream consumers that re-entitle off
|
|
57
|
-
// `result.transactionId` (e.g. a host app registering its own pass)
|
|
58
|
-
// received `undefined` and silently failed — the user was charged but
|
|
59
|
-
// never granted. Returning the stored record keeps the receipt chain
|
|
60
|
-
// intact and is safe: ownership was already proven by the prior validated
|
|
61
|
-
// purchase recorded under this userId (same idempotent-restore semantics
|
|
62
|
-
// as the transactionId-match path below).
|
|
63
|
-
if (type === PURCHASE_TYPE.NON_CONSUMABLE) {
|
|
64
|
-
const owned = (await purchaseStore.getPurchasesByUserId(userId)).find((p) => p.productId === productId);
|
|
65
|
-
if (owned) {
|
|
66
|
-
const response = {
|
|
67
|
-
valid: true,
|
|
68
|
-
purchase: { ...owned, userId },
|
|
69
|
-
action: 'restored',
|
|
70
|
-
};
|
|
71
|
-
res.status(200).json(response);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// Validate receipt via the appropriate platform-specific product validator.
|
|
76
|
-
// Note: these are separate from the subscription validators — they call
|
|
77
|
-
// purchases.products (not purchases.subscriptions) and apply product-specific
|
|
78
|
-
// security checks (type validation, consumptionState, receipt age).
|
|
79
|
-
let transactionId = null;
|
|
80
|
-
let purchasedAt = new Date().toISOString();
|
|
81
|
-
if (platform === 'apple') {
|
|
82
|
-
if (!config.apple) {
|
|
83
|
-
sendError(res, 500, ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, 'Apple configuration not provided', NO_PURCHASE);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const result = await validateAppleConsumableReceipt(receipt, config.apple, productId);
|
|
87
|
-
if (result) {
|
|
88
|
-
transactionId = result.transactionId;
|
|
89
|
-
purchasedAt = result.purchasedAt;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
if (!config.google) {
|
|
94
|
-
sendError(res, 500, ONESUB_ERROR_CODE.GOOGLE_CONFIG_MISSING, 'Google configuration not provided', NO_PURCHASE);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const result = await validateGoogleProductReceipt(receipt, productId, config.google, type === PURCHASE_TYPE.CONSUMABLE ? 'consumable' : 'non_consumable');
|
|
98
|
-
if (result) {
|
|
99
|
-
transactionId = result.transactionId;
|
|
100
|
-
purchasedAt = result.purchasedAt;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (!transactionId) {
|
|
104
|
-
sendError(res, 422, ONESUB_ERROR_CODE.RECEIPT_VALIDATION_FAILED, 'Receipt validation failed', NO_PURCHASE);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
// Check if this transactionId has been seen before.
|
|
108
|
-
// - Same userId → idempotent restore (e.g. network retry)
|
|
109
|
-
// - Different userId + non_consumable → reassign (device reinstall)
|
|
110
|
-
// - Different userId + consumable → reject (receipts can't be reused)
|
|
111
|
-
// - No existing row → fresh purchase, INSERT below
|
|
112
|
-
const existing = await purchaseStore.getPurchaseByTransactionId(transactionId);
|
|
113
|
-
let action = 'new';
|
|
114
|
-
if (existing) {
|
|
115
|
-
action = 'restored';
|
|
116
|
-
if (existing.userId !== userId) {
|
|
117
|
-
if (type === PURCHASE_TYPE.NON_CONSUMABLE) {
|
|
118
|
-
await purchaseStore.reassignPurchase(transactionId, userId);
|
|
119
|
-
log.info(`[onesub/purchase] reassigned transaction ${transactionId} from ${existing.userId} to ${userId}`);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
sendError(res, 409, ONESUB_ERROR_CODE.TRANSACTION_BELONGS_TO_OTHER_USER, 'TRANSACTION_BELONGS_TO_OTHER_USER', NO_PURCHASE);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const response = {
|
|
127
|
-
valid: true,
|
|
128
|
-
purchase: { ...existing, userId },
|
|
129
|
-
action,
|
|
130
|
-
};
|
|
131
|
-
res.status(200).json(response);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const purchase = {
|
|
135
|
-
userId,
|
|
136
|
-
productId,
|
|
137
|
-
platform,
|
|
138
|
-
type,
|
|
139
|
-
transactionId,
|
|
140
|
-
purchasedAt,
|
|
141
|
-
quantity: 1,
|
|
142
|
-
};
|
|
143
|
-
await purchaseStore.savePurchase(purchase);
|
|
144
|
-
// Google requires acknowledgement within 3 days of purchase or the
|
|
145
|
-
// transaction is auto-refunded.
|
|
146
|
-
// - Consumables: :consume implicitly acknowledges, so consume is enough.
|
|
147
|
-
// - Non-consumables: must call :acknowledge explicitly.
|
|
148
|
-
// Both must run after savePurchase — if called before, a DB failure would
|
|
149
|
-
// leave the receipt acknowledged/consumed but the entitlement ungranted
|
|
150
|
-
// with no retry path.
|
|
151
|
-
if (platform === 'google' && config.google) {
|
|
152
|
-
if (type === PURCHASE_TYPE.CONSUMABLE) {
|
|
153
|
-
void consumeGoogleProductReceipt(receipt, productId, config.google);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
void acknowledgeGoogleProduct(receipt, productId, config.google);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
const response = {
|
|
160
|
-
valid: true,
|
|
161
|
-
purchase,
|
|
162
|
-
action,
|
|
163
|
-
};
|
|
164
|
-
res.status(200).json(response);
|
|
165
|
-
}
|
|
166
|
-
catch (err) {
|
|
167
|
-
log.error('[onesub/purchase/validate] Unexpected error:', err);
|
|
168
|
-
sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, 'Internal server error during purchase validation', NO_PURCHASE);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
/**
|
|
172
|
-
* GET /onesub/purchase/status?userId=xxx[&productId=yyy]
|
|
173
|
-
*
|
|
174
|
-
* Returns all purchases for a user, optionally filtered by productId.
|
|
175
|
-
*/
|
|
176
|
-
router.get(ROUTES.PURCHASE_STATUS, async (req, res) => {
|
|
177
|
-
let query;
|
|
178
|
-
try {
|
|
179
|
-
query = purchaseStatusQuerySchema.parse(req.query);
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
if (err instanceof z.ZodError) {
|
|
183
|
-
sendZodError(res, err, { purchases: [] });
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
throw err;
|
|
187
|
-
}
|
|
188
|
-
const { userId, productId } = query;
|
|
189
|
-
try {
|
|
190
|
-
let purchases = await purchaseStore.getPurchasesByUserId(userId);
|
|
191
|
-
if (productId !== undefined) {
|
|
192
|
-
purchases = purchases.filter((p) => p.productId === productId);
|
|
193
|
-
}
|
|
194
|
-
const response = { purchases };
|
|
195
|
-
res.status(200).json(response);
|
|
196
|
-
}
|
|
197
|
-
catch (err) {
|
|
198
|
-
log.error('[onesub/purchase/status] Store error:', err);
|
|
199
|
-
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error', { purchases: [] });
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
return router;
|
|
203
|
-
}
|
|
204
|
-
//# sourceMappingURL=purchase.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"purchase.js","sourceRoot":"","sources":["../../src/routes/purchase.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/status.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { ROUTES, SUBSCRIPTION_STATUS, ONESUB_ERROR_CODE } from '@onesub/shared';
|
|
3
|
-
import { log } from '../logger.js';
|
|
4
|
-
import { sendError } from '../errors.js';
|
|
5
|
-
const NO_SUB = { active: false, subscription: null };
|
|
6
|
-
export function createStatusRouter(store) {
|
|
7
|
-
const router = Router();
|
|
8
|
-
/**
|
|
9
|
-
* GET /onesub/status?userId=xxx
|
|
10
|
-
*
|
|
11
|
-
* Returns whether the user has an active subscription and the full
|
|
12
|
-
* SubscriptionInfo if one exists.
|
|
13
|
-
*/
|
|
14
|
-
router.get(ROUTES.STATUS, async (req, res) => {
|
|
15
|
-
const userId = req.query['userId'];
|
|
16
|
-
if (!userId || typeof userId !== 'string') {
|
|
17
|
-
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, 'Missing required query param: userId', NO_SUB);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (userId.length > 256) {
|
|
21
|
-
sendError(res, 400, ONESUB_ERROR_CODE.USER_ID_TOO_LONG, 'userId must not exceed 256 characters', NO_SUB);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
const sub = await store.getByUserId(userId);
|
|
26
|
-
if (!sub) {
|
|
27
|
-
const response = { active: false, subscription: null };
|
|
28
|
-
res.status(200).json(response);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
// Entitlement is valid during the store-granted grace period (Apple
|
|
32
|
-
// GRACE_PERIOD subtype, Google IN_GRACE_PERIOD). on_hold is excluded —
|
|
33
|
-
// the user must fix payment before they regain access.
|
|
34
|
-
//
|
|
35
|
-
// The expiresAt check guards two cases:
|
|
36
|
-
// 1. refundPolicy='until_expiry' lets a refunded record keep
|
|
37
|
-
// status='active' until the original expiry passes — without this
|
|
38
|
-
// check, the user would keep entitlement forever if Apple/Google
|
|
39
|
-
// never sent the matching EXPIRED notification.
|
|
40
|
-
// 2. Stale records where the EXPIRED webhook was missed entirely.
|
|
41
|
-
const statusAllows = sub.status === SUBSCRIPTION_STATUS.ACTIVE ||
|
|
42
|
-
sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD;
|
|
43
|
-
const notYetExpired = new Date(sub.expiresAt).getTime() > Date.now();
|
|
44
|
-
const active = statusAllows && notYetExpired;
|
|
45
|
-
const response = { active, subscription: sub };
|
|
46
|
-
res.status(200).json(response);
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
log.error('[onesub/status] Store error:', err);
|
|
50
|
-
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error', NO_SUB);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
return router;
|
|
54
|
-
}
|
|
55
|
-
//# sourceMappingURL=status.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEhF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAW,CAAC;AAE9D,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACzD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QAC9D,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAEnC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1C,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,aAAa,EAAE,sCAAsC,EAAE,MAAM,CAAC,CAAC;YACrG,OAAO;QACT,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACxB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,gBAAgB,EAAE,uCAAuC,EAAE,MAAM,CAAC,CAAC;YACzG,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAE5C,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,MAAM,QAAQ,GAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;gBACvE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YAED,oEAAoE;YACpE,uEAAuE;YACvE,uDAAuD;YACvD,EAAE;YACF,wCAAwC;YACxC,+DAA+D;YAC/D,uEAAuE;YACvE,sEAAsE;YACtE,qDAAqD;YACrD,oEAAoE;YACpE,MAAM,YAAY,GAChB,GAAG,CAAC,MAAM,KAAK,mBAAmB,CAAC,MAAM;gBACzC,GAAG,CAAC,MAAM,KAAK,mBAAmB,CAAC,YAAY,CAAC;YAClD,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACrE,MAAM,MAAM,GAAG,YAAY,IAAI,aAAa,CAAC;YAC7C,MAAM,QAAQ,GAAmB,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;YAC/D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;YAC/C,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,WAAW,EAAE,uBAAuB,EAAE,MAAM,CAAC,CAAC;QACtF,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|