@onesub/server 0.21.0 → 0.21.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAiD7E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAW7F;AAGD,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC5G,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAK/E,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAO9D,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAIxF,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAWnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAwD7E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAW7F;AAGD,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC5G,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAK/E,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAO9D,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAIxF,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1271,6 +1271,16 @@ function sendZodError(res, err2, extra = {}) {
1271
1271
  extra
1272
1272
  );
1273
1273
  }
1274
+ function parseOrSend(res, schema, input, opts = {}) {
1275
+ const result = schema.safeParse(input);
1276
+ if (result.success) return result.data;
1277
+ if (opts.message !== void 0) {
1278
+ sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, opts.message, opts.extra ?? {});
1279
+ } else {
1280
+ sendZodError(res, result.error, opts.extra ?? {});
1281
+ }
1282
+ return void 0;
1283
+ }
1274
1284
 
1275
1285
  // src/apps.ts
1276
1286
  function appName(app) {
@@ -1369,20 +1379,9 @@ function createValidateRouter(config, store) {
1369
1379
  const router = Router();
1370
1380
  const registry = getAppRegistry(config);
1371
1381
  router.post(ROUTES.VALIDATE, async (req, res) => {
1372
- let platform;
1373
- let receipt;
1374
- let userId;
1375
- let productId;
1376
- let appId;
1377
- try {
1378
- ({ platform, receipt, userId, productId, appId } = validateSchema.parse(req.body));
1379
- } catch (err2) {
1380
- if (err2 instanceof z.ZodError) {
1381
- sendZodError(res, err2, NO_SUB);
1382
- return;
1383
- }
1384
- throw err2;
1385
- }
1382
+ const body = parseOrSend(res, validateSchema, req.body, { extra: NO_SUB });
1383
+ if (!body) return;
1384
+ const { platform, receipt, userId, productId, appId } = body;
1386
1385
  try {
1387
1386
  let sub = null;
1388
1387
  const appConfig = registry.configFor({
@@ -1745,6 +1744,21 @@ var GOOGLE_FAILURE_MESSAGES = {
1745
1744
  message: "Failed to process notification"
1746
1745
  }
1747
1746
  };
1747
+ function warnIfGoogleWebhookOpen(config) {
1748
+ if (process.env["NODE_ENV"] !== "production") return;
1749
+ const apps = getAppRegistry(config).apps;
1750
+ const googleApps = apps.map((a) => a.google).filter((g) => !!g);
1751
+ if (googleApps.length > 0 && !googleApps.some((g) => g.pushAudience)) {
1752
+ log.warn(
1753
+ "[onesub] SECURITY: POST /onesub/webhook/google accepts unauthenticated requests \u2014 no configured app sets google.pushAudience, so the Pub/Sub OIDC token is never verified. A caller who knows a purchaseToken or orderId can cancel a subscription or delete a one-time purchase. Set google.pushAudience (and google.pushServiceAccountEmail)."
1754
+ );
1755
+ }
1756
+ if (!apps.some((a) => a.google?.packageName)) {
1757
+ log.warn(
1758
+ "[onesub] POST /onesub/webhook/google is in open mode \u2014 no configured app declares google.packageName, so notifications for ANY package are accepted. The route is mounted whether or not Google is configured; set google.packageName, or block the path at your proxy if this deployment does not serve Google Play."
1759
+ );
1760
+ }
1761
+ }
1748
1762
  function googleResolver(config) {
1749
1763
  const registry = getAppRegistry(config);
1750
1764
  const restricted = registry.apps.some((app) => !!app.google?.packageName);
@@ -1991,16 +2005,8 @@ function createPurchaseRouter(config, purchaseStore) {
1991
2005
  const router = Router();
1992
2006
  const registry = getAppRegistry(config);
1993
2007
  router.post(ROUTES.VALIDATE_PURCHASE, async (req, res) => {
1994
- let body;
1995
- try {
1996
- body = validatePurchaseSchema.parse(req.body);
1997
- } catch (err2) {
1998
- if (err2 instanceof z.ZodError) {
1999
- sendZodError(res, err2, NO_PURCHASE);
2000
- return;
2001
- }
2002
- throw err2;
2003
- }
2008
+ const body = parseOrSend(res, validatePurchaseSchema, req.body, { extra: NO_PURCHASE });
2009
+ if (!body) return;
2004
2010
  const { platform, receipt, userId, productId, type, appId } = body;
2005
2011
  const appConfig = registry.configFor({
2006
2012
  appId,
@@ -2126,16 +2132,10 @@ function createPurchaseRouter(config, purchaseStore) {
2126
2132
  }
2127
2133
  });
2128
2134
  router.get(ROUTES.PURCHASE_STATUS, async (req, res) => {
2129
- let query;
2130
- try {
2131
- query = purchaseStatusQuerySchema.parse(req.query);
2132
- } catch (err2) {
2133
- if (err2 instanceof z.ZodError) {
2134
- sendZodError(res, err2, { purchases: [] });
2135
- return;
2136
- }
2137
- throw err2;
2138
- }
2135
+ const query = parseOrSend(res, purchaseStatusQuerySchema, req.query, {
2136
+ extra: { purchases: [] }
2137
+ });
2138
+ if (!query) return;
2139
2139
  const { userId, productId } = query;
2140
2140
  try {
2141
2141
  const purchases = productId !== void 0 ? await purchasesForProduct(purchaseStore, userId, productId) : await purchaseStore.getPurchasesByUserId(userId);
@@ -2183,6 +2183,8 @@ async function evaluateEntitlement(userId, entitlement, store, purchaseStore) {
2183
2183
  }
2184
2184
  var userIdSchema = z.string().min(1).max(256);
2185
2185
  var entitlementIdSchema = z.string().min(1).max(128);
2186
+ var entitlementQuerySchema = z.object({ userId: userIdSchema, id: entitlementIdSchema });
2187
+ var entitlementsQuerySchema = z.object({ userId: userIdSchema });
2186
2188
  function createEntitlementRouter(config, store, purchaseStore) {
2187
2189
  if (!config.entitlements || Object.keys(config.entitlements).length === 0) {
2188
2190
  return null;
@@ -2190,15 +2192,11 @@ function createEntitlementRouter(config, store, purchaseStore) {
2190
2192
  const entitlements = config.entitlements;
2191
2193
  const router = Router();
2192
2194
  router.get(ROUTES.ENTITLEMENT, async (req, res) => {
2193
- let userId;
2194
- let id;
2195
- try {
2196
- userId = userIdSchema.parse(req.query["userId"]);
2197
- id = entitlementIdSchema.parse(req.query["id"]);
2198
- } catch {
2199
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "userId and id are required");
2200
- return;
2201
- }
2195
+ const query = parseOrSend(res, entitlementQuerySchema, req.query, {
2196
+ message: "userId and id are required"
2197
+ });
2198
+ if (!query) return;
2199
+ const { userId, id } = query;
2202
2200
  const entitlement = entitlements[id];
2203
2201
  if (!entitlement) {
2204
2202
  sendError(res, 404, ONESUB_ERROR_CODE.ENTITLEMENT_NOT_FOUND, `Unknown entitlement: ${id}`);
@@ -2214,13 +2212,11 @@ function createEntitlementRouter(config, store, purchaseStore) {
2214
2212
  }
2215
2213
  });
2216
2214
  router.get(ROUTES.ENTITLEMENTS, async (req, res) => {
2217
- let userId;
2218
- try {
2219
- userId = userIdSchema.parse(req.query["userId"]);
2220
- } catch {
2221
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "userId is required");
2222
- return;
2223
- }
2215
+ const query = parseOrSend(res, entitlementsQuerySchema, req.query, {
2216
+ message: "userId is required"
2217
+ });
2218
+ if (!query) return;
2219
+ const { userId } = query;
2224
2220
  try {
2225
2221
  const [subs, purchases] = await Promise.all([
2226
2222
  store.getAllByUserId(userId),
@@ -2269,13 +2265,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2269
2265
  productId: z.string().min(1).max(256)
2270
2266
  });
2271
2267
  router.delete("/onesub/purchase/admin/:userId/:productId", async (req, res) => {
2272
- let params;
2273
- try {
2274
- params = resetParamsSchema.parse(req.params);
2275
- } catch {
2276
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "userId and productId required");
2277
- return;
2278
- }
2268
+ const params = parseOrSend(res, resetParamsSchema, req.params, {
2269
+ message: "userId and productId required"
2270
+ });
2271
+ if (!params) return;
2279
2272
  const deleted = await purchaseStore.deletePurchases(params.userId, params.productId);
2280
2273
  res.json({ ok: true, deleted });
2281
2274
  });
@@ -2284,16 +2277,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2284
2277
  newUserId: z.string().min(1).max(256)
2285
2278
  });
2286
2279
  router.post("/onesub/purchase/admin/transfer", async (req, res) => {
2287
- let body;
2288
- try {
2289
- body = transferSchema.parse(req.body);
2290
- } catch (err2) {
2291
- if (err2 instanceof z.ZodError) {
2292
- sendZodError(res, err2);
2293
- return;
2294
- }
2295
- throw err2;
2296
- }
2280
+ const body = parseOrSend(res, transferSchema, req.body);
2281
+ if (!body) return;
2297
2282
  const existing = await purchaseStore.getPurchaseByTransactionId(body.transactionId);
2298
2283
  if (!existing) {
2299
2284
  sendError(res, 404, ONESUB_ERROR_CODE.TRANSACTION_NOT_FOUND, "TRANSACTION_NOT_FOUND");
@@ -2315,16 +2300,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2315
2300
  transactionId: z.string().min(1).max(256).optional()
2316
2301
  });
2317
2302
  router.post("/onesub/purchase/admin/grant", async (req, res) => {
2318
- let body;
2319
- try {
2320
- body = grantSchema.parse(req.body);
2321
- } catch (err2) {
2322
- if (err2 instanceof z.ZodError) {
2323
- sendZodError(res, err2);
2324
- return;
2325
- }
2326
- throw err2;
2327
- }
2303
+ const body = parseOrSend(res, grantSchema, req.body);
2304
+ if (!body) return;
2328
2305
  const transactionId = body.transactionId ?? `admin_grant_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
2329
2306
  const purchase = {
2330
2307
  transactionId,
@@ -2356,16 +2333,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2356
2333
  offset: z.coerce.number().int().nonnegative().optional()
2357
2334
  });
2358
2335
  router.get(ROUTES.ADMIN_SUBSCRIPTIONS, async (req, res) => {
2359
- let query;
2360
- try {
2361
- query = listQuerySchema.parse(req.query);
2362
- } catch (err2) {
2363
- if (err2 instanceof z.ZodError) {
2364
- sendZodError(res, err2);
2365
- return;
2366
- }
2367
- throw err2;
2368
- }
2336
+ const query = parseOrSend(res, listQuerySchema, req.query);
2337
+ if (!query) return;
2369
2338
  try {
2370
2339
  const result = await store.listFiltered(query);
2371
2340
  const response = {
@@ -2376,20 +2345,18 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2376
2345
  };
2377
2346
  res.status(200).json(response);
2378
2347
  } catch (err2) {
2379
- sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "list error");
2348
+ log.error("[onesub/admin/subscriptions] list error:", err2);
2349
+ sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2380
2350
  }
2381
2351
  });
2382
2352
  const detailParamsSchema = z.object({
2383
2353
  transactionId: z.string().min(1).max(256)
2384
2354
  });
2385
2355
  router.get("/onesub/admin/subscriptions/:transactionId", async (req, res) => {
2386
- let params;
2387
- try {
2388
- params = detailParamsSchema.parse(req.params);
2389
- } catch {
2390
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "transactionId required");
2391
- return;
2392
- }
2356
+ const params = parseOrSend(res, detailParamsSchema, req.params, {
2357
+ message: "transactionId required"
2358
+ });
2359
+ if (!params) return;
2393
2360
  try {
2394
2361
  const sub = await store.getByTransactionId(params.transactionId);
2395
2362
  if (!sub) {
@@ -2398,20 +2365,18 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2398
2365
  }
2399
2366
  res.status(200).json(sub);
2400
2367
  } catch (err2) {
2401
- sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "detail error");
2368
+ log.error("[onesub/admin/subscriptions/:transactionId] detail error:", err2);
2369
+ sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2402
2370
  }
2403
2371
  });
2404
2372
  const customerParamsSchema = z.object({
2405
2373
  userId: z.string().min(1).max(256)
2406
2374
  });
2407
2375
  router.get("/onesub/admin/customers/:userId", async (req, res) => {
2408
- let params;
2409
- try {
2410
- params = customerParamsSchema.parse(req.params);
2411
- } catch {
2412
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "userId required");
2413
- return;
2414
- }
2376
+ const params = parseOrSend(res, customerParamsSchema, req.params, {
2377
+ message: "userId required"
2378
+ });
2379
+ if (!params) return;
2415
2380
  try {
2416
2381
  const [subscriptions, purchases] = await Promise.all([
2417
2382
  store.getAllByUserId(params.userId),
@@ -2433,20 +2398,18 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2433
2398
  };
2434
2399
  res.status(200).json(response);
2435
2400
  } catch (err2) {
2436
- sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "customer error");
2401
+ log.error("[onesub/admin/customers/:userId] error:", err2);
2402
+ sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2437
2403
  }
2438
2404
  });
2439
2405
  const syncAppleParamsSchema = z.object({
2440
2406
  originalTransactionId: z.string().min(1).max(256)
2441
2407
  });
2442
2408
  router.post(ROUTES.ADMIN_SYNC_APPLE, async (req, res) => {
2443
- let params;
2444
- try {
2445
- params = syncAppleParamsSchema.parse(req.params);
2446
- } catch {
2447
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "originalTransactionId required");
2448
- return;
2449
- }
2409
+ const params = parseOrSend(res, syncAppleParamsSchema, req.params, {
2410
+ message: "originalTransactionId required"
2411
+ });
2412
+ if (!params) return;
2450
2413
  if (!config.apple?.issuerId || !config.apple?.keyId || !config.apple?.privateKey) {
2451
2414
  sendError(res, 400, ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "Apple API credentials not configured");
2452
2415
  return;
@@ -2465,7 +2428,8 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2465
2428
  await store.save(fresh);
2466
2429
  res.status(200).json({ ok: true, subscription: fresh });
2467
2430
  } catch (err2) {
2468
- sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, err2.message ?? "sync error");
2431
+ log.error("[onesub/admin/sync-apple] error:", err2);
2432
+ sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error");
2469
2433
  }
2470
2434
  });
2471
2435
  const overrideParamsSchema = z.object({ userId: z.string().min(1).max(256) });
@@ -2474,19 +2438,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2474
2438
  res.json({ overrides: listTestOverrides() });
2475
2439
  });
2476
2440
  router.put(ROUTES.ADMIN_TEST_OVERRIDE, (req, res) => {
2477
- let params;
2478
- let body;
2479
- try {
2480
- params = overrideParamsSchema.parse(req.params);
2481
- body = overrideBodySchema.parse(req.body);
2482
- } catch (err2) {
2483
- if (err2 instanceof z.ZodError) {
2484
- sendZodError(res, err2);
2485
- } else {
2486
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "userId and { entitled: boolean } required");
2487
- }
2488
- return;
2489
- }
2441
+ const params = parseOrSend(res, overrideParamsSchema, req.params);
2442
+ if (!params) return;
2443
+ const body = parseOrSend(res, overrideBodySchema, req.body);
2444
+ if (!body) return;
2490
2445
  setTestOverride(params.userId, body.entitled);
2491
2446
  log.warn(
2492
2447
  `[onesub/admin] sandbox test override set for ${params.userId}: entitled=${body.entitled}`
@@ -2494,13 +2449,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2494
2449
  res.json({ ok: true, userId: params.userId, entitled: body.entitled, sandboxOnly: true });
2495
2450
  });
2496
2451
  router.delete(ROUTES.ADMIN_TEST_OVERRIDE, (req, res) => {
2497
- let params;
2498
- try {
2499
- params = overrideParamsSchema.parse(req.params);
2500
- } catch {
2501
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "userId required");
2502
- return;
2503
- }
2452
+ const params = parseOrSend(res, overrideParamsSchema, req.params, {
2453
+ message: "userId required"
2454
+ });
2455
+ if (!params) return;
2504
2456
  const cleared = clearTestOverride(params.userId);
2505
2457
  res.json({ ok: true, userId: params.userId, cleared });
2506
2458
  });
@@ -2510,25 +2462,22 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2510
2462
  const items = await webhookQueue.listDeadLetters();
2511
2463
  res.status(200).json({ items });
2512
2464
  } catch (err2) {
2513
- sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "list error");
2465
+ log.error("[onesub/admin/webhook-deadletters] error:", err2);
2466
+ sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2514
2467
  }
2515
2468
  });
2516
2469
  }
2517
2470
  if (webhookQueue?.replayDeadLetter) {
2518
2471
  const replayParamsSchema = z.object({ id: z.string().min(1).max(256) });
2519
2472
  router.post("/onesub/admin/webhook-replay/:id", async (req, res) => {
2520
- let params;
2521
- try {
2522
- params = replayParamsSchema.parse(req.params);
2523
- } catch {
2524
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "id required");
2525
- return;
2526
- }
2473
+ const params = parseOrSend(res, replayParamsSchema, req.params, { message: "id required" });
2474
+ if (!params) return;
2527
2475
  try {
2528
2476
  await webhookQueue.replayDeadLetter(params.id);
2529
2477
  res.status(200).json({ ok: true });
2530
2478
  } catch (err2) {
2531
- sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, err2.message ?? "replay error");
2479
+ log.error("[onesub/admin/webhook-replay] error:", err2);
2480
+ sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2532
2481
  }
2533
2482
  });
2534
2483
  }
@@ -2787,16 +2736,8 @@ function createAppleOfferRouter(config) {
2787
2736
  return;
2788
2737
  }
2789
2738
  }
2790
- let body;
2791
- try {
2792
- body = offerBodySchema.parse(req.body);
2793
- } catch (err2) {
2794
- if (err2 instanceof z.ZodError) {
2795
- sendZodError(res, err2);
2796
- return;
2797
- }
2798
- throw err2;
2799
- }
2739
+ const body = parseOrSend(res, offerBodySchema, req.body);
2740
+ if (!body) return;
2800
2741
  if (!apple.bundleId) {
2801
2742
  sendError(res, 400, ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "config.apple.bundleId is required for offer signing");
2802
2743
  return;
@@ -2813,7 +2754,8 @@ function createAppleOfferRouter(config) {
2813
2754
  );
2814
2755
  res.status(200).json(result);
2815
2756
  } catch (err2) {
2816
- sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, err2.message ?? "Offer signing failed");
2757
+ log.error("[onesub/apple/offer] signing error:", err2);
2758
+ sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Offer signing failed");
2817
2759
  }
2818
2760
  });
2819
2761
  return router;
@@ -4215,6 +4157,7 @@ function createOneSubMiddleware(config) {
4215
4157
  "[onesub] apple.mockMode / google.mockMode cannot be enabled when NODE_ENV=production \u2014 these modes accept any receipt as valid."
4216
4158
  );
4217
4159
  }
4160
+ warnIfGoogleWebhookOpen(config);
4218
4161
  const store = config.store ?? new InMemorySubscriptionStore();
4219
4162
  const purchaseStore = config.purchaseStore ?? new InMemoryPurchaseStore();
4220
4163
  if (config.cache) setDefaultCache(config.cache);