@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.
@@ -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
@@ -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
- let platform;
1380
- let receipt;
1381
- let userId;
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
- let body;
2002
- try {
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
- let query;
2137
- try {
2138
- query = purchaseStatusQuerySchema.parse(req.query);
2139
- } catch (err2) {
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
- let userId;
2201
- let id;
2202
- try {
2203
- userId = userIdSchema.parse(req.query["userId"]);
2204
- id = entitlementIdSchema.parse(req.query["id"]);
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
- let userId;
2225
- try {
2226
- userId = userIdSchema.parse(req.query["userId"]);
2227
- } catch {
2228
- sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "userId is required");
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
- let params;
2280
- try {
2281
- params = resetParamsSchema.parse(req.params);
2282
- } catch {
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
- let body;
2295
- try {
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
- let body;
2326
- try {
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
- let query;
2367
- try {
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
- sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "list error");
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
- let params;
2394
- try {
2395
- params = detailParamsSchema.parse(req.params);
2396
- } catch {
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
- sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "detail error");
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
- let params;
2416
- try {
2417
- params = customerParamsSchema.parse(req.params);
2418
- } catch {
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
- sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "customer error");
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
- let params;
2451
- try {
2452
- params = syncAppleParamsSchema.parse(req.params);
2453
- } catch {
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
- sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, err2.message ?? "sync error");
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
- let params;
2485
- let body;
2486
- try {
2487
- params = overrideParamsSchema.parse(req.params);
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
- let params;
2505
- try {
2506
- params = overrideParamsSchema.parse(req.params);
2507
- } catch {
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
- sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "list error");
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
- let params;
2528
- try {
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
- sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "replay error");
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
- let body;
2798
- try {
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
- sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, err2.message ?? "Offer signing failed");
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;