@onesub/server 0.21.0 → 0.21.1
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__/errors.test.d.ts +12 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.cjs +79 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +79 -152
- package/dist/index.js.map +1 -1
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/apple-offer.d.ts.map +1 -1
- package/dist/routes/entitlements.d.ts.map +1 -1
- package/dist/routes/purchase.d.ts.map +1 -1
- package/dist/routes/validate.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error-response helpers, and the guarantee that a 500 does not describe the
|
|
3
|
+
* server's internals to the caller.
|
|
4
|
+
*
|
|
5
|
+
* `parseOrSend` replaced three hand-rolled input-validation shapes across the
|
|
6
|
+
* routes. The parts worth pinning down are the ones a caller can observe: the
|
|
7
|
+
* status, the `errorCode`, whether the route's response shape survives on the
|
|
8
|
+
* error path, and — for the admin routes, which used to forward
|
|
9
|
+
* `(err as Error).message` verbatim — that internal failure text stays server-side.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=errors.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/errors.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -14,4 +14,38 @@ export declare function sendError(res: Response, status: number, code: OneSubErr
|
|
|
14
14
|
* are joined for the human-readable `error` field.
|
|
15
15
|
*/
|
|
16
16
|
export declare function sendZodError(res: Response, err: z.ZodError, extra?: Record<string, unknown>): void;
|
|
17
|
+
export interface ParseOrSendOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Route-specific fields to merge into the error body, so a 400 keeps the
|
|
20
|
+
* shape the route's success response has (`subscription: null`,
|
|
21
|
+
* `purchases: []`, and so on).
|
|
22
|
+
*/
|
|
23
|
+
extra?: Record<string, unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Send this one message instead of the per-issue zod detail. Use it where the
|
|
26
|
+
* route deliberately does not echo the input shape back — URL params, and the
|
|
27
|
+
* entitlement routes. Omit it to get the field-level detail.
|
|
28
|
+
*/
|
|
29
|
+
message?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse `input` with `schema`, or send a 400 and return `undefined`.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const body = parseOrSend(res, validateSchema, req.body, { extra: NO_SUB });
|
|
36
|
+
* if (!body) return;
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Every route was hand-rolling this, and in three different shapes: a
|
|
40
|
+
* `try/catch` that re-threw non-zod errors, a `try/catch {}` that swallowed
|
|
41
|
+
* everything into one generic message, and `safeParse`. The first is correct but
|
|
42
|
+
* five lines per call site; the second silently turns a genuine bug inside the
|
|
43
|
+
* schema — a bad refinement, a throwing transform — into "400 bad input", which
|
|
44
|
+
* is the wrong status and hides the cause.
|
|
45
|
+
*
|
|
46
|
+
* This keeps the correct behaviour and makes it the short one: zod failures
|
|
47
|
+
* become a 400, and anything else propagates to the route's own error handling
|
|
48
|
+
* rather than being reported as the caller's fault.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseOrSend<S extends z.ZodType>(res: Response, schema: S, input: unknown, opts?: ParseOrSendOptions): z.output<S> | undefined;
|
|
17
51
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,CAAC,CAAC,QAAQ,EACf,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,IAAI,CAQN"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,CAAC,CAAC,QAAQ,EACf,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,IAAI,CAQN;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAC7C,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,OAAO,EACd,IAAI,GAAE,kBAAuB,GAC5B,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAUzB"}
|
package/dist/index.cjs
CHANGED
|
@@ -1278,6 +1278,16 @@ function sendZodError(res, err2, extra = {}) {
|
|
|
1278
1278
|
extra
|
|
1279
1279
|
);
|
|
1280
1280
|
}
|
|
1281
|
+
function parseOrSend(res, schema, input, opts = {}) {
|
|
1282
|
+
const result = schema.safeParse(input);
|
|
1283
|
+
if (result.success) return result.data;
|
|
1284
|
+
if (opts.message !== void 0) {
|
|
1285
|
+
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, opts.message, opts.extra ?? {});
|
|
1286
|
+
} else {
|
|
1287
|
+
sendZodError(res, result.error, opts.extra ?? {});
|
|
1288
|
+
}
|
|
1289
|
+
return void 0;
|
|
1290
|
+
}
|
|
1281
1291
|
|
|
1282
1292
|
// src/apps.ts
|
|
1283
1293
|
function appName(app) {
|
|
@@ -1376,20 +1386,9 @@ function createValidateRouter(config, store) {
|
|
|
1376
1386
|
const router = express.Router();
|
|
1377
1387
|
const registry = getAppRegistry(config);
|
|
1378
1388
|
router.post(shared.ROUTES.VALIDATE, async (req, res) => {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
let productId;
|
|
1383
|
-
let appId;
|
|
1384
|
-
try {
|
|
1385
|
-
({ platform, receipt, userId, productId, appId } = validateSchema.parse(req.body));
|
|
1386
|
-
} catch (err2) {
|
|
1387
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
1388
|
-
sendZodError(res, err2, NO_SUB);
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
throw err2;
|
|
1392
|
-
}
|
|
1389
|
+
const body = parseOrSend(res, validateSchema, req.body, { extra: NO_SUB });
|
|
1390
|
+
if (!body) return;
|
|
1391
|
+
const { platform, receipt, userId, productId, appId } = body;
|
|
1393
1392
|
try {
|
|
1394
1393
|
let sub = null;
|
|
1395
1394
|
const appConfig = registry.configFor({
|
|
@@ -1998,16 +1997,8 @@ function createPurchaseRouter(config, purchaseStore) {
|
|
|
1998
1997
|
const router = express.Router();
|
|
1999
1998
|
const registry = getAppRegistry(config);
|
|
2000
1999
|
router.post(shared.ROUTES.VALIDATE_PURCHASE, async (req, res) => {
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
body = validatePurchaseSchema.parse(req.body);
|
|
2004
|
-
} catch (err2) {
|
|
2005
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
2006
|
-
sendZodError(res, err2, NO_PURCHASE);
|
|
2007
|
-
return;
|
|
2008
|
-
}
|
|
2009
|
-
throw err2;
|
|
2010
|
-
}
|
|
2000
|
+
const body = parseOrSend(res, validatePurchaseSchema, req.body, { extra: NO_PURCHASE });
|
|
2001
|
+
if (!body) return;
|
|
2011
2002
|
const { platform, receipt, userId, productId, type, appId } = body;
|
|
2012
2003
|
const appConfig = registry.configFor({
|
|
2013
2004
|
appId,
|
|
@@ -2133,16 +2124,10 @@ function createPurchaseRouter(config, purchaseStore) {
|
|
|
2133
2124
|
}
|
|
2134
2125
|
});
|
|
2135
2126
|
router.get(shared.ROUTES.PURCHASE_STATUS, async (req, res) => {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
2141
|
-
sendZodError(res, err2, { purchases: [] });
|
|
2142
|
-
return;
|
|
2143
|
-
}
|
|
2144
|
-
throw err2;
|
|
2145
|
-
}
|
|
2127
|
+
const query = parseOrSend(res, purchaseStatusQuerySchema, req.query, {
|
|
2128
|
+
extra: { purchases: [] }
|
|
2129
|
+
});
|
|
2130
|
+
if (!query) return;
|
|
2146
2131
|
const { userId, productId } = query;
|
|
2147
2132
|
try {
|
|
2148
2133
|
const purchases = productId !== void 0 ? await purchasesForProduct(purchaseStore, userId, productId) : await purchaseStore.getPurchasesByUserId(userId);
|
|
@@ -2190,6 +2175,8 @@ async function evaluateEntitlement(userId, entitlement, store, purchaseStore) {
|
|
|
2190
2175
|
}
|
|
2191
2176
|
var userIdSchema = zod.z.string().min(1).max(256);
|
|
2192
2177
|
var entitlementIdSchema = zod.z.string().min(1).max(128);
|
|
2178
|
+
var entitlementQuerySchema = zod.z.object({ userId: userIdSchema, id: entitlementIdSchema });
|
|
2179
|
+
var entitlementsQuerySchema = zod.z.object({ userId: userIdSchema });
|
|
2193
2180
|
function createEntitlementRouter(config, store, purchaseStore) {
|
|
2194
2181
|
if (!config.entitlements || Object.keys(config.entitlements).length === 0) {
|
|
2195
2182
|
return null;
|
|
@@ -2197,15 +2184,11 @@ function createEntitlementRouter(config, store, purchaseStore) {
|
|
|
2197
2184
|
const entitlements = config.entitlements;
|
|
2198
2185
|
const router = express.Router();
|
|
2199
2186
|
router.get(shared.ROUTES.ENTITLEMENT, async (req, res) => {
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
} catch {
|
|
2206
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "userId and id are required");
|
|
2207
|
-
return;
|
|
2208
|
-
}
|
|
2187
|
+
const query = parseOrSend(res, entitlementQuerySchema, req.query, {
|
|
2188
|
+
message: "userId and id are required"
|
|
2189
|
+
});
|
|
2190
|
+
if (!query) return;
|
|
2191
|
+
const { userId, id } = query;
|
|
2209
2192
|
const entitlement = entitlements[id];
|
|
2210
2193
|
if (!entitlement) {
|
|
2211
2194
|
sendError(res, 404, shared.ONESUB_ERROR_CODE.ENTITLEMENT_NOT_FOUND, `Unknown entitlement: ${id}`);
|
|
@@ -2221,13 +2204,11 @@ function createEntitlementRouter(config, store, purchaseStore) {
|
|
|
2221
2204
|
}
|
|
2222
2205
|
});
|
|
2223
2206
|
router.get(shared.ROUTES.ENTITLEMENTS, async (req, res) => {
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
return;
|
|
2230
|
-
}
|
|
2207
|
+
const query = parseOrSend(res, entitlementsQuerySchema, req.query, {
|
|
2208
|
+
message: "userId is required"
|
|
2209
|
+
});
|
|
2210
|
+
if (!query) return;
|
|
2211
|
+
const { userId } = query;
|
|
2231
2212
|
try {
|
|
2232
2213
|
const [subs, purchases] = await Promise.all([
|
|
2233
2214
|
store.getAllByUserId(userId),
|
|
@@ -2276,13 +2257,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2276
2257
|
productId: zod.z.string().min(1).max(256)
|
|
2277
2258
|
});
|
|
2278
2259
|
router.delete("/onesub/purchase/admin/:userId/:productId", async (req, res) => {
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "userId and productId required");
|
|
2284
|
-
return;
|
|
2285
|
-
}
|
|
2260
|
+
const params = parseOrSend(res, resetParamsSchema, req.params, {
|
|
2261
|
+
message: "userId and productId required"
|
|
2262
|
+
});
|
|
2263
|
+
if (!params) return;
|
|
2286
2264
|
const deleted = await purchaseStore.deletePurchases(params.userId, params.productId);
|
|
2287
2265
|
res.json({ ok: true, deleted });
|
|
2288
2266
|
});
|
|
@@ -2291,16 +2269,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2291
2269
|
newUserId: zod.z.string().min(1).max(256)
|
|
2292
2270
|
});
|
|
2293
2271
|
router.post("/onesub/purchase/admin/transfer", async (req, res) => {
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
body = transferSchema.parse(req.body);
|
|
2297
|
-
} catch (err2) {
|
|
2298
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
2299
|
-
sendZodError(res, err2);
|
|
2300
|
-
return;
|
|
2301
|
-
}
|
|
2302
|
-
throw err2;
|
|
2303
|
-
}
|
|
2272
|
+
const body = parseOrSend(res, transferSchema, req.body);
|
|
2273
|
+
if (!body) return;
|
|
2304
2274
|
const existing = await purchaseStore.getPurchaseByTransactionId(body.transactionId);
|
|
2305
2275
|
if (!existing) {
|
|
2306
2276
|
sendError(res, 404, shared.ONESUB_ERROR_CODE.TRANSACTION_NOT_FOUND, "TRANSACTION_NOT_FOUND");
|
|
@@ -2322,16 +2292,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2322
2292
|
transactionId: zod.z.string().min(1).max(256).optional()
|
|
2323
2293
|
});
|
|
2324
2294
|
router.post("/onesub/purchase/admin/grant", async (req, res) => {
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
body = grantSchema.parse(req.body);
|
|
2328
|
-
} catch (err2) {
|
|
2329
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
2330
|
-
sendZodError(res, err2);
|
|
2331
|
-
return;
|
|
2332
|
-
}
|
|
2333
|
-
throw err2;
|
|
2334
|
-
}
|
|
2295
|
+
const body = parseOrSend(res, grantSchema, req.body);
|
|
2296
|
+
if (!body) return;
|
|
2335
2297
|
const transactionId = body.transactionId ?? `admin_grant_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
2336
2298
|
const purchase = {
|
|
2337
2299
|
transactionId,
|
|
@@ -2363,16 +2325,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2363
2325
|
offset: zod.z.coerce.number().int().nonnegative().optional()
|
|
2364
2326
|
});
|
|
2365
2327
|
router.get(shared.ROUTES.ADMIN_SUBSCRIPTIONS, async (req, res) => {
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
query = listQuerySchema.parse(req.query);
|
|
2369
|
-
} catch (err2) {
|
|
2370
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
2371
|
-
sendZodError(res, err2);
|
|
2372
|
-
return;
|
|
2373
|
-
}
|
|
2374
|
-
throw err2;
|
|
2375
|
-
}
|
|
2328
|
+
const query = parseOrSend(res, listQuerySchema, req.query);
|
|
2329
|
+
if (!query) return;
|
|
2376
2330
|
try {
|
|
2377
2331
|
const result = await store.listFiltered(query);
|
|
2378
2332
|
const response = {
|
|
@@ -2383,20 +2337,18 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2383
2337
|
};
|
|
2384
2338
|
res.status(200).json(response);
|
|
2385
2339
|
} catch (err2) {
|
|
2386
|
-
|
|
2340
|
+
log.error("[onesub/admin/subscriptions] list error:", err2);
|
|
2341
|
+
sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2387
2342
|
}
|
|
2388
2343
|
});
|
|
2389
2344
|
const detailParamsSchema = zod.z.object({
|
|
2390
2345
|
transactionId: zod.z.string().min(1).max(256)
|
|
2391
2346
|
});
|
|
2392
2347
|
router.get("/onesub/admin/subscriptions/:transactionId", async (req, res) => {
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "transactionId required");
|
|
2398
|
-
return;
|
|
2399
|
-
}
|
|
2348
|
+
const params = parseOrSend(res, detailParamsSchema, req.params, {
|
|
2349
|
+
message: "transactionId required"
|
|
2350
|
+
});
|
|
2351
|
+
if (!params) return;
|
|
2400
2352
|
try {
|
|
2401
2353
|
const sub = await store.getByTransactionId(params.transactionId);
|
|
2402
2354
|
if (!sub) {
|
|
@@ -2405,20 +2357,18 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2405
2357
|
}
|
|
2406
2358
|
res.status(200).json(sub);
|
|
2407
2359
|
} catch (err2) {
|
|
2408
|
-
|
|
2360
|
+
log.error("[onesub/admin/subscriptions/:transactionId] detail error:", err2);
|
|
2361
|
+
sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2409
2362
|
}
|
|
2410
2363
|
});
|
|
2411
2364
|
const customerParamsSchema = zod.z.object({
|
|
2412
2365
|
userId: zod.z.string().min(1).max(256)
|
|
2413
2366
|
});
|
|
2414
2367
|
router.get("/onesub/admin/customers/:userId", async (req, res) => {
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "userId required");
|
|
2420
|
-
return;
|
|
2421
|
-
}
|
|
2368
|
+
const params = parseOrSend(res, customerParamsSchema, req.params, {
|
|
2369
|
+
message: "userId required"
|
|
2370
|
+
});
|
|
2371
|
+
if (!params) return;
|
|
2422
2372
|
try {
|
|
2423
2373
|
const [subscriptions, purchases] = await Promise.all([
|
|
2424
2374
|
store.getAllByUserId(params.userId),
|
|
@@ -2440,20 +2390,18 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2440
2390
|
};
|
|
2441
2391
|
res.status(200).json(response);
|
|
2442
2392
|
} catch (err2) {
|
|
2443
|
-
|
|
2393
|
+
log.error("[onesub/admin/customers/:userId] error:", err2);
|
|
2394
|
+
sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2444
2395
|
}
|
|
2445
2396
|
});
|
|
2446
2397
|
const syncAppleParamsSchema = zod.z.object({
|
|
2447
2398
|
originalTransactionId: zod.z.string().min(1).max(256)
|
|
2448
2399
|
});
|
|
2449
2400
|
router.post(shared.ROUTES.ADMIN_SYNC_APPLE, async (req, res) => {
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "originalTransactionId required");
|
|
2455
|
-
return;
|
|
2456
|
-
}
|
|
2401
|
+
const params = parseOrSend(res, syncAppleParamsSchema, req.params, {
|
|
2402
|
+
message: "originalTransactionId required"
|
|
2403
|
+
});
|
|
2404
|
+
if (!params) return;
|
|
2457
2405
|
if (!config.apple?.issuerId || !config.apple?.keyId || !config.apple?.privateKey) {
|
|
2458
2406
|
sendError(res, 400, shared.ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "Apple API credentials not configured");
|
|
2459
2407
|
return;
|
|
@@ -2472,7 +2420,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2472
2420
|
await store.save(fresh);
|
|
2473
2421
|
res.status(200).json({ ok: true, subscription: fresh });
|
|
2474
2422
|
} catch (err2) {
|
|
2475
|
-
|
|
2423
|
+
log.error("[onesub/admin/sync-apple] error:", err2);
|
|
2424
|
+
sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error");
|
|
2476
2425
|
}
|
|
2477
2426
|
});
|
|
2478
2427
|
const overrideParamsSchema = zod.z.object({ userId: zod.z.string().min(1).max(256) });
|
|
@@ -2481,19 +2430,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2481
2430
|
res.json({ overrides: listTestOverrides() });
|
|
2482
2431
|
});
|
|
2483
2432
|
router.put(shared.ROUTES.ADMIN_TEST_OVERRIDE, (req, res) => {
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
body = overrideBodySchema.parse(req.body);
|
|
2489
|
-
} catch (err2) {
|
|
2490
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
2491
|
-
sendZodError(res, err2);
|
|
2492
|
-
} else {
|
|
2493
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "userId and { entitled: boolean } required");
|
|
2494
|
-
}
|
|
2495
|
-
return;
|
|
2496
|
-
}
|
|
2433
|
+
const params = parseOrSend(res, overrideParamsSchema, req.params);
|
|
2434
|
+
if (!params) return;
|
|
2435
|
+
const body = parseOrSend(res, overrideBodySchema, req.body);
|
|
2436
|
+
if (!body) return;
|
|
2497
2437
|
setTestOverride(params.userId, body.entitled);
|
|
2498
2438
|
log.warn(
|
|
2499
2439
|
`[onesub/admin] sandbox test override set for ${params.userId}: entitled=${body.entitled}`
|
|
@@ -2501,13 +2441,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2501
2441
|
res.json({ ok: true, userId: params.userId, entitled: body.entitled, sandboxOnly: true });
|
|
2502
2442
|
});
|
|
2503
2443
|
router.delete(shared.ROUTES.ADMIN_TEST_OVERRIDE, (req, res) => {
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "userId required");
|
|
2509
|
-
return;
|
|
2510
|
-
}
|
|
2444
|
+
const params = parseOrSend(res, overrideParamsSchema, req.params, {
|
|
2445
|
+
message: "userId required"
|
|
2446
|
+
});
|
|
2447
|
+
if (!params) return;
|
|
2511
2448
|
const cleared = clearTestOverride(params.userId);
|
|
2512
2449
|
res.json({ ok: true, userId: params.userId, cleared });
|
|
2513
2450
|
});
|
|
@@ -2517,25 +2454,22 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2517
2454
|
const items = await webhookQueue.listDeadLetters();
|
|
2518
2455
|
res.status(200).json({ items });
|
|
2519
2456
|
} catch (err2) {
|
|
2520
|
-
|
|
2457
|
+
log.error("[onesub/admin/webhook-deadletters] error:", err2);
|
|
2458
|
+
sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2521
2459
|
}
|
|
2522
2460
|
});
|
|
2523
2461
|
}
|
|
2524
2462
|
if (webhookQueue?.replayDeadLetter) {
|
|
2525
2463
|
const replayParamsSchema = zod.z.object({ id: zod.z.string().min(1).max(256) });
|
|
2526
2464
|
router.post("/onesub/admin/webhook-replay/:id", async (req, res) => {
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
params = replayParamsSchema.parse(req.params);
|
|
2530
|
-
} catch {
|
|
2531
|
-
sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "id required");
|
|
2532
|
-
return;
|
|
2533
|
-
}
|
|
2465
|
+
const params = parseOrSend(res, replayParamsSchema, req.params, { message: "id required" });
|
|
2466
|
+
if (!params) return;
|
|
2534
2467
|
try {
|
|
2535
2468
|
await webhookQueue.replayDeadLetter(params.id);
|
|
2536
2469
|
res.status(200).json({ ok: true });
|
|
2537
2470
|
} catch (err2) {
|
|
2538
|
-
|
|
2471
|
+
log.error("[onesub/admin/webhook-replay] error:", err2);
|
|
2472
|
+
sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2539
2473
|
}
|
|
2540
2474
|
});
|
|
2541
2475
|
}
|
|
@@ -2794,16 +2728,8 @@ function createAppleOfferRouter(config) {
|
|
|
2794
2728
|
return;
|
|
2795
2729
|
}
|
|
2796
2730
|
}
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
body = offerBodySchema.parse(req.body);
|
|
2800
|
-
} catch (err2) {
|
|
2801
|
-
if (err2 instanceof zod.z.ZodError) {
|
|
2802
|
-
sendZodError(res, err2);
|
|
2803
|
-
return;
|
|
2804
|
-
}
|
|
2805
|
-
throw err2;
|
|
2806
|
-
}
|
|
2731
|
+
const body = parseOrSend(res, offerBodySchema, req.body);
|
|
2732
|
+
if (!body) return;
|
|
2807
2733
|
if (!apple.bundleId) {
|
|
2808
2734
|
sendError(res, 400, shared.ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "config.apple.bundleId is required for offer signing");
|
|
2809
2735
|
return;
|
|
@@ -2820,7 +2746,8 @@ function createAppleOfferRouter(config) {
|
|
|
2820
2746
|
);
|
|
2821
2747
|
res.status(200).json(result);
|
|
2822
2748
|
} catch (err2) {
|
|
2823
|
-
|
|
2749
|
+
log.error("[onesub/apple/offer] signing error:", err2);
|
|
2750
|
+
sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, "Offer signing failed");
|
|
2824
2751
|
}
|
|
2825
2752
|
});
|
|
2826
2753
|
return router;
|