@onesub/server 0.12.0 → 0.12.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.
Files changed (120) hide show
  1. package/dist/__tests__/webhook-apple.test.d.ts +12 -0
  2. package/dist/__tests__/webhook-apple.test.d.ts.map +1 -0
  3. package/dist/__tests__/webhook-google.test.d.ts +13 -0
  4. package/dist/__tests__/webhook-google.test.d.ts.map +1 -0
  5. package/dist/index.cjs +258 -288
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +259 -286
  10. package/dist/index.js.map +1 -1
  11. package/dist/providers/google.d.ts.map +1 -1
  12. package/dist/routes/webhook-apple.d.ts +6 -0
  13. package/dist/routes/webhook-apple.d.ts.map +1 -0
  14. package/dist/routes/webhook-google.d.ts +7 -0
  15. package/dist/routes/webhook-google.d.ts.map +1 -0
  16. package/dist/routes/webhook.d.ts +4 -17
  17. package/dist/routes/webhook.d.ts.map +1 -1
  18. package/package.json +2 -2
  19. package/dist/__tests__/admin-customer-detail.test.js +0 -146
  20. package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
  21. package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
  22. package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
  23. package/dist/__tests__/admin-subscription-detail.test.js +0 -112
  24. package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
  25. package/dist/__tests__/apple-root-ca.test.js +0 -41
  26. package/dist/__tests__/apple-root-ca.test.js.map +0 -1
  27. package/dist/__tests__/apple-status-api.test.js +0 -345
  28. package/dist/__tests__/apple-status-api.test.js.map +0 -1
  29. package/dist/__tests__/cache.test.js +0 -34
  30. package/dist/__tests__/cache.test.js.map +0 -1
  31. package/dist/__tests__/entitlements.test.js +0 -268
  32. package/dist/__tests__/entitlements.test.js.map +0 -1
  33. package/dist/__tests__/error-codes.test.js +0 -168
  34. package/dist/__tests__/error-codes.test.js.map +0 -1
  35. package/dist/__tests__/fetch-hardening.test.js +0 -192
  36. package/dist/__tests__/fetch-hardening.test.js.map +0 -1
  37. package/dist/__tests__/google-paused.test.js +0 -126
  38. package/dist/__tests__/google-paused.test.js.map +0 -1
  39. package/dist/__tests__/google-price-change.test.js +0 -186
  40. package/dist/__tests__/google-price-change.test.js.map +0 -1
  41. package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
  42. package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
  43. package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
  44. package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
  45. package/dist/__tests__/lifecycle-states.test.js +0 -423
  46. package/dist/__tests__/lifecycle-states.test.js.map +0 -1
  47. package/dist/__tests__/linked-purchase-token.test.js +0 -227
  48. package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
  49. package/dist/__tests__/metrics.test.js +0 -340
  50. package/dist/__tests__/metrics.test.js.map +0 -1
  51. package/dist/__tests__/mock-provider.test.js +0 -188
  52. package/dist/__tests__/mock-provider.test.js.map +0 -1
  53. package/dist/__tests__/openapi.test.js +0 -32
  54. package/dist/__tests__/openapi.test.js.map +0 -1
  55. package/dist/__tests__/paused-auto-resume.test.js +0 -162
  56. package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
  57. package/dist/__tests__/providers.test.js +0 -321
  58. package/dist/__tests__/providers.test.js.map +0 -1
  59. package/dist/__tests__/redis-store.test.js +0 -117
  60. package/dist/__tests__/redis-store.test.js.map +0 -1
  61. package/dist/__tests__/refund-policy.test.js +0 -317
  62. package/dist/__tests__/refund-policy.test.js.map +0 -1
  63. package/dist/__tests__/schema.test.js +0 -46
  64. package/dist/__tests__/schema.test.js.map +0 -1
  65. package/dist/__tests__/store.test.js +0 -172
  66. package/dist/__tests__/store.test.js.map +0 -1
  67. package/dist/__tests__/test-utils.js +0 -30
  68. package/dist/__tests__/test-utils.js.map +0 -1
  69. package/dist/__tests__/webhook-events.test.js +0 -36
  70. package/dist/__tests__/webhook-events.test.js.map +0 -1
  71. package/dist/__tests__/webhook-queue.test.js +0 -26
  72. package/dist/__tests__/webhook-queue.test.js.map +0 -1
  73. package/dist/__tests__/webhook-refund.test.js +0 -313
  74. package/dist/__tests__/webhook-refund.test.js.map +0 -1
  75. package/dist/cache.js +0 -43
  76. package/dist/cache.js.map +0 -1
  77. package/dist/errors.js +0 -19
  78. package/dist/errors.js.map +0 -1
  79. package/dist/http.js +0 -36
  80. package/dist/http.js.map +0 -1
  81. package/dist/logger.js +0 -20
  82. package/dist/logger.js.map +0 -1
  83. package/dist/openapi.js +0 -196
  84. package/dist/openapi.js.map +0 -1
  85. package/dist/providers/apple-root-ca.js +0 -38
  86. package/dist/providers/apple-root-ca.js.map +0 -1
  87. package/dist/providers/apple.js +0 -510
  88. package/dist/providers/apple.js.map +0 -1
  89. package/dist/providers/google.js +0 -522
  90. package/dist/providers/google.js.map +0 -1
  91. package/dist/providers/mock.js +0 -93
  92. package/dist/providers/mock.js.map +0 -1
  93. package/dist/routes/admin.js +0 -287
  94. package/dist/routes/admin.js.map +0 -1
  95. package/dist/routes/entitlements.js +0 -130
  96. package/dist/routes/entitlements.js.map +0 -1
  97. package/dist/routes/metrics.js +0 -281
  98. package/dist/routes/metrics.js.map +0 -1
  99. package/dist/routes/purchase.js +0 -187
  100. package/dist/routes/purchase.js.map +0 -1
  101. package/dist/routes/status.js +0 -55
  102. package/dist/routes/status.js.map +0 -1
  103. package/dist/routes/validate.js +0 -70
  104. package/dist/routes/validate.js.map +0 -1
  105. package/dist/routes/webhook.js +0 -461
  106. package/dist/routes/webhook.js.map +0 -1
  107. package/dist/store.js +0 -145
  108. package/dist/store.js.map +0 -1
  109. package/dist/stores/postgres.js +0 -329
  110. package/dist/stores/postgres.js.map +0 -1
  111. package/dist/stores/redis.js +0 -255
  112. package/dist/stores/redis.js.map +0 -1
  113. package/dist/stores/schema.js +0 -67
  114. package/dist/stores/schema.js.map +0 -1
  115. package/dist/tracing.js +0 -74
  116. package/dist/tracing.js.map +0 -1
  117. package/dist/webhook-events.js +0 -57
  118. package/dist/webhook-events.js.map +0 -1
  119. package/dist/webhook-queue.js +0 -108
  120. package/dist/webhook-queue.js.map +0 -1
@@ -1,522 +0,0 @@
1
- import { SUBSCRIPTION_STATUS } from '@onesub/shared';
2
- import { log } from '../logger.js';
3
- import { fetchWithTimeout } from '../http.js';
4
- import { getDefaultCache } from '../cache.js';
5
- import { mockValidateGoogleSubscription, mockValidateGoogleProduct, } from './mock.js';
6
- /**
7
- * Google RTDN notification types.
8
- * https://developer.android.com/google/play/billing/rtdn-reference
9
- */
10
- const GOOGLE_NOTIFICATION_TYPE = {
11
- SUBSCRIPTION_RECOVERED: 1,
12
- SUBSCRIPTION_RENEWED: 2,
13
- SUBSCRIPTION_CANCELED: 3,
14
- SUBSCRIPTION_PURCHASED: 4,
15
- SUBSCRIPTION_ON_HOLD: 5,
16
- SUBSCRIPTION_IN_GRACE_PERIOD: 6,
17
- SUBSCRIPTION_RESTARTED: 7,
18
- SUBSCRIPTION_PRICE_CHANGE_CONFIRMED: 8,
19
- SUBSCRIPTION_DEFERRED: 9,
20
- SUBSCRIPTION_PAUSED: 10,
21
- SUBSCRIPTION_PAUSE_SCHEDULE_CHANGED: 11,
22
- SUBSCRIPTION_REVOKED: 12,
23
- SUBSCRIPTION_EXPIRED: 13,
24
- };
25
- /** Maximum age for product receipts (72 hours). */
26
- const MAX_PRODUCT_RECEIPT_AGE_MS = 72 * 60 * 60 * 1000;
27
- /**
28
- * Per-key in-flight token mint promises. Used to deduplicate concurrent
29
- * refreshes inside a single process — the cluster-wide cache layer sits in
30
- * `getDefaultCache()` underneath. Without this map every concurrent caller in
31
- * the same process would fire a separate network refresh while waiting for
32
- * the new value to land in Redis / memory.
33
- */
34
- const refreshPromises = new Map();
35
- /**
36
- * Build the cache key for a given service account.
37
- *
38
- * Hashing the key (instead of using it raw) keeps the key length bounded for
39
- * cache backends that have key-size limits, and avoids leaking partial JSON
40
- * into log lines that print cache keys.
41
- */
42
- function googleTokenCacheKey(serviceAccountKey) {
43
- let hash = 0;
44
- for (let i = 0; i < serviceAccountKey.length; i++) {
45
- hash = (hash * 31 + serviceAccountKey.charCodeAt(i)) | 0;
46
- }
47
- return `google:oauth:${hash}`;
48
- }
49
- /**
50
- * Obtain a Google OAuth2 access token, returning a cached token if it has more
51
- * than 60 seconds of remaining validity. Google tokens are valid for 3600 seconds,
52
- * so this avoids a network round-trip on every API call.
53
- *
54
- * Cache layer: `getDefaultCache()` (in-memory by default, swappable with
55
- * Redis / Memcached via `setDefaultCache()`). When the cache is shared across
56
- * cluster nodes only one node refreshes per TTL window.
57
- *
58
- * Promise deduplication prevents a thundering herd inside a single process:
59
- * concurrent callers that arrive while the token is being refreshed all await
60
- * the same in-flight request instead of each issuing their own.
61
- */
62
- async function getCachedAccessToken(serviceAccountKey) {
63
- const cache = getDefaultCache();
64
- const cacheKey = googleTokenCacheKey(serviceAccountKey);
65
- const cached = await cache.get(cacheKey);
66
- if (cached && cached.expiresAt - Date.now() > 60_000) {
67
- return cached.token;
68
- }
69
- const inflight = refreshPromises.get(cacheKey);
70
- if (inflight)
71
- return inflight;
72
- const promise = getAccessToken(serviceAccountKey)
73
- .then(async (token) => {
74
- const expiresAt = Date.now() + 3_600_000;
75
- // TTL slightly under the 1h Google validity — cache eviction matches token expiry.
76
- await cache.set(cacheKey, { token, expiresAt }, 3_540);
77
- return token;
78
- })
79
- .finally(() => {
80
- refreshPromises.delete(cacheKey);
81
- });
82
- refreshPromises.set(cacheKey, promise);
83
- return promise;
84
- }
85
- /**
86
- * Obtain a Google OAuth2 access token using a service account key (JWT assertion flow).
87
- * The serviceAccountKey is expected to be a JSON string of the service account credentials.
88
- */
89
- async function getAccessToken(serviceAccountKey) {
90
- let key;
91
- try {
92
- key = JSON.parse(serviceAccountKey);
93
- }
94
- catch {
95
- throw new Error('[onesub/google] Invalid serviceAccountKey JSON');
96
- }
97
- const tokenUri = key.token_uri ?? 'https://oauth2.googleapis.com/token';
98
- const scope = 'https://www.googleapis.com/auth/androidpublisher';
99
- const now = Math.floor(Date.now() / 1000);
100
- // Build a JWT assertion
101
- const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url');
102
- const payload = Buffer.from(JSON.stringify({
103
- iss: key.client_email,
104
- scope,
105
- aud: tokenUri,
106
- iat: now,
107
- exp: now + 3600,
108
- })).toString('base64url');
109
- const signingInput = `${header}.${payload}`;
110
- // Sign with the private key using the native crypto module
111
- const { createSign } = await import('crypto');
112
- const sign = createSign('RSA-SHA256');
113
- sign.update(signingInput);
114
- const signature = sign.sign(key.private_key, 'base64url');
115
- const assertion = `${signingInput}.${signature}`;
116
- const resp = await fetchWithTimeout(tokenUri, {
117
- method: 'POST',
118
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
119
- body: new URLSearchParams({
120
- grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
121
- assertion,
122
- }),
123
- });
124
- if (!resp.ok) {
125
- throw new Error(`[onesub/google] Token request failed: ${resp.status}`);
126
- }
127
- const data = (await resp.json());
128
- if (!data.access_token)
129
- throw new Error('[onesub/google] No access_token in response');
130
- return data.access_token;
131
- }
132
- /**
133
- * Fetch a subscription purchase from the Google Play Developer API
134
- * (purchases.subscriptionsv2.get). The v2 endpoint takes only the purchaseToken
135
- * — the productId comes back in lineItems, which lets one token represent a
136
- * multi-product subscription.
137
- */
138
- async function fetchSubscriptionPurchaseV2(packageName, purchaseToken, accessToken) {
139
- const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/` +
140
- `${encodeURIComponent(packageName)}/purchases/subscriptionsv2/tokens/` +
141
- `${encodeURIComponent(purchaseToken)}`;
142
- const resp = await fetchWithTimeout(url, {
143
- headers: { Authorization: `Bearer ${accessToken}` },
144
- });
145
- if (!resp.ok) {
146
- const body = await resp.text();
147
- throw new Error(`[onesub/google] Play API v2 error ${resp.status}: ${body}`);
148
- }
149
- return resp.json();
150
- }
151
- /**
152
- * Fetch a one-time product purchase from the Google Play Developer API.
153
- * Uses purchases.products instead of purchases.subscriptions.
154
- */
155
- async function fetchProductPurchase(packageName, productId, purchaseToken, accessToken) {
156
- const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/` +
157
- `${encodeURIComponent(packageName)}/purchases/products/` +
158
- `${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}`;
159
- const resp = await fetchWithTimeout(url, {
160
- headers: { Authorization: `Bearer ${accessToken}` },
161
- });
162
- if (!resp.ok) {
163
- const body = await resp.text();
164
- throw new Error(`[onesub/google] Play Products API error ${resp.status}: ${body}`);
165
- }
166
- return resp.json();
167
- }
168
- /**
169
- * Acknowledge a Google Play subscription purchase.
170
- * Google auto-refunds purchases that are not acknowledged within 3 days.
171
- *
172
- * Idempotent on Google's side: a no-op when the purchase has already been
173
- * acknowledged. Fire-and-forget — entitlement was already granted, so failures
174
- * are logged but not surfaced to the caller (operations should monitor logs).
175
- */
176
- export async function acknowledgeGoogleSubscription(purchaseToken, productId, config) {
177
- if (config.mockMode)
178
- return;
179
- if (!config.serviceAccountKey)
180
- return;
181
- let accessToken;
182
- try {
183
- accessToken = await getCachedAccessToken(config.serviceAccountKey);
184
- }
185
- catch (err) {
186
- log.warn('[onesub/google] Could not get access token for subscription ack:', err);
187
- return;
188
- }
189
- const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/` +
190
- `${encodeURIComponent(config.packageName)}/purchases/subscriptions/` +
191
- `${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:acknowledge`;
192
- try {
193
- const resp = await fetchWithTimeout(url, {
194
- method: 'POST',
195
- headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
196
- body: '{}',
197
- });
198
- if (!resp.ok) {
199
- const body = await resp.text();
200
- log.warn(`[onesub/google] Subscription acknowledge API error ${resp.status}: ${body} — auto-refund risk`);
201
- }
202
- }
203
- catch (err) {
204
- log.warn('[onesub/google] Subscription acknowledge network error — auto-refund risk:', err);
205
- }
206
- }
207
- /**
208
- * Acknowledge a Google Play one-time product purchase (non-consumable).
209
- * Consumables do not need this — :consume implicitly acknowledges.
210
- *
211
- * Same fire-and-forget semantics as acknowledgeGoogleSubscription.
212
- */
213
- export async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
214
- if (config.mockMode)
215
- return;
216
- if (!config.serviceAccountKey)
217
- return;
218
- let accessToken;
219
- try {
220
- accessToken = await getCachedAccessToken(config.serviceAccountKey);
221
- }
222
- catch (err) {
223
- log.warn('[onesub/google] Could not get access token for product ack:', err);
224
- return;
225
- }
226
- const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/` +
227
- `${encodeURIComponent(config.packageName)}/purchases/products/` +
228
- `${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:acknowledge`;
229
- try {
230
- const resp = await fetchWithTimeout(url, {
231
- method: 'POST',
232
- headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
233
- body: '{}',
234
- });
235
- if (!resp.ok) {
236
- const body = await resp.text();
237
- log.warn(`[onesub/google] Product acknowledge API error ${resp.status}: ${body} — auto-refund risk`);
238
- }
239
- }
240
- catch (err) {
241
- log.warn('[onesub/google] Product acknowledge network error — auto-refund risk:', err);
242
- }
243
- }
244
- /**
245
- * Consume a one-time product purchase via the Google Play Developer API.
246
- * Must be called for consumables after the entitlement is granted to the user.
247
- * Google auto-refunds unconsumed purchases after 3 days.
248
- *
249
- * This function does not throw — callers should log failures and monitor,
250
- * as the entitlement has already been granted.
251
- */
252
- export async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
253
- if (!config.serviceAccountKey)
254
- return;
255
- let accessToken;
256
- try {
257
- accessToken = await getCachedAccessToken(config.serviceAccountKey);
258
- }
259
- catch (err) {
260
- log.warn('[onesub/google] Could not get access token for consume:', err);
261
- return;
262
- }
263
- const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/` +
264
- `${encodeURIComponent(config.packageName)}/purchases/products/` +
265
- `${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:consume`;
266
- try {
267
- const resp = await fetchWithTimeout(url, {
268
- method: 'POST',
269
- headers: { Authorization: `Bearer ${accessToken}` },
270
- });
271
- if (!resp.ok) {
272
- const body = await resp.text();
273
- log.warn(`[onesub/google] Consume API error ${resp.status}: ${body} — auto-refund risk`);
274
- }
275
- }
276
- catch (err) {
277
- log.warn('[onesub/google] Consume network error — auto-refund risk:', err);
278
- }
279
- }
280
- /**
281
- * Map a v2 subscriptionState string to onesub SubscriptionStatus.
282
- *
283
- * Returns null when the state is unrecognised or PENDING (initial purchase
284
- * not yet settled — entitlement should not be granted yet).
285
- */
286
- function deriveStatusV2(state) {
287
- switch (state) {
288
- case 'SUBSCRIPTION_STATE_ACTIVE':
289
- return SUBSCRIPTION_STATUS.ACTIVE;
290
- case 'SUBSCRIPTION_STATE_IN_GRACE_PERIOD':
291
- return SUBSCRIPTION_STATUS.GRACE_PERIOD;
292
- case 'SUBSCRIPTION_STATE_ON_HOLD':
293
- return SUBSCRIPTION_STATUS.ON_HOLD;
294
- case 'SUBSCRIPTION_STATE_PAUSED':
295
- return SUBSCRIPTION_STATUS.PAUSED;
296
- case 'SUBSCRIPTION_STATE_CANCELED':
297
- return SUBSCRIPTION_STATUS.CANCELED;
298
- case 'SUBSCRIPTION_STATE_EXPIRED':
299
- return SUBSCRIPTION_STATUS.EXPIRED;
300
- case 'SUBSCRIPTION_STATE_PENDING':
301
- case 'SUBSCRIPTION_STATE_UNSPECIFIED':
302
- default:
303
- return null;
304
- }
305
- }
306
- /**
307
- * Validate a Google Play purchase token via purchases.subscriptionsv2.get.
308
- *
309
- * The productId argument is used to pick the matching `lineItems` entry. If
310
- * the response has no lineItem with that productId, validation fails — the
311
- * token does not entitle the caller to that product. (For multi-product
312
- * subscriptions, callers can read other lineItems out of the API directly.)
313
- *
314
- * @param receipt The purchaseToken from the client (Google Play Billing)
315
- * @param productId Expected subscription productId — must match a lineItem
316
- * @param config Google config with packageName + optional serviceAccountKey
317
- */
318
- export async function validateGoogleReceipt(receipt, productId, config) {
319
- if (config.mockMode)
320
- return mockValidateGoogleSubscription(receipt, productId);
321
- if (!config.serviceAccountKey) {
322
- log.warn('[onesub/google] No serviceAccountKey provided — cannot call Play API');
323
- return null;
324
- }
325
- let purchase;
326
- try {
327
- const token = await getCachedAccessToken(config.serviceAccountKey);
328
- purchase = await fetchSubscriptionPurchaseV2(config.packageName, receipt, token);
329
- }
330
- catch (err) {
331
- log.error('[onesub/google] Receipt validation failed:', err);
332
- return null;
333
- }
334
- const status = deriveStatusV2(purchase.subscriptionState);
335
- if (!status) {
336
- log.warn('[onesub/google] Unrecognised or pending subscriptionState — rejecting:', purchase.subscriptionState);
337
- return null;
338
- }
339
- // Pick the lineItem matching the requested productId. v2 supports multi-product
340
- // subscriptions so the same token can carry several lineItems; we only entitle
341
- // the caller for the productId they explicitly asked about.
342
- const lineItem = purchase.lineItems?.find((item) => item.productId === productId);
343
- if (!lineItem) {
344
- log.warn('[onesub/google] productId not found in subscription lineItems:', productId, 'available:', purchase.lineItems?.map((i) => i.productId).join(', ') ?? '(none)');
345
- return null;
346
- }
347
- const expiresAt = lineItem.expiryTime ?? new Date().toISOString();
348
- const purchasedAt = purchase.startTime ?? new Date().toISOString();
349
- const willRenew = lineItem.autoRenewingPlan?.autoRenewEnabled ?? false;
350
- // Surface autoResumeTime only when the subscription is actually paused —
351
- // pausedStateContext is undefined for any other state, but be defensive in
352
- // case Google starts including it on adjacent states (e.g. recently-resumed).
353
- const autoResumeTime = status === SUBSCRIPTION_STATUS.PAUSED
354
- ? purchase.pausedStateContext?.autoResumeTime
355
- : undefined;
356
- return {
357
- userId: '', // caller fills this in
358
- productId,
359
- platform: 'google',
360
- status,
361
- expiresAt,
362
- originalTransactionId: purchase.latestOrderId ?? receipt.slice(0, 64),
363
- purchasedAt,
364
- willRenew,
365
- // Surface the linked token so host apps (and our webhook userId-continuity
366
- // logic) can follow upgrade/downgrade chains.
367
- linkedPurchaseToken: purchase.linkedPurchaseToken,
368
- autoResumeTime,
369
- };
370
- }
371
- /**
372
- * Validate a Google Play purchase token for a one-time product (consumable or
373
- * non-consumable). Uses purchases.products, not purchases.subscriptions.
374
- *
375
- * Security checks applied beyond basic API verification:
376
- * - purchaseState must be 0 (completed)
377
- * - consumptionState checked for consumables: already-consumed tokens indicate replay
378
- * - Receipt age limited to 72 hours
379
- * - orderId used as transactionId (per-purchase unique, unlike purchaseToken)
380
- */
381
- export async function validateGoogleProductReceipt(purchaseToken, productId, config, type = 'non_consumable') {
382
- if (config.mockMode)
383
- return mockValidateGoogleProduct(purchaseToken, productId);
384
- if (!config.serviceAccountKey) {
385
- log.warn('[onesub/google] No serviceAccountKey — cannot validate product receipt');
386
- return null;
387
- }
388
- let purchase;
389
- try {
390
- const token = await getCachedAccessToken(config.serviceAccountKey);
391
- purchase = await fetchProductPurchase(config.packageName, productId, purchaseToken, token);
392
- }
393
- catch (err) {
394
- log.error('[onesub/google] Product receipt validation failed:', err);
395
- return null;
396
- }
397
- // purchaseState 0 = completed (1 = canceled, 2 = pending)
398
- if (purchase.purchaseState !== 0) {
399
- log.warn('[onesub/google] Purchase not completed, state:', purchase.purchaseState);
400
- return null;
401
- }
402
- // For consumables: consumptionState 1 means already consumed by a previous request.
403
- // This is the primary replay-attack signal for consumables on Android.
404
- if (type === 'consumable' && purchase.consumptionState === 1) {
405
- log.warn('[onesub/google] Consumable already consumed — possible replay attack');
406
- return null;
407
- }
408
- // Reject receipts older than 72 hours
409
- if (purchase.purchaseTimeMillis) {
410
- const purchaseTime = parseInt(purchase.purchaseTimeMillis, 10);
411
- if (Date.now() - purchaseTime > MAX_PRODUCT_RECEIPT_AGE_MS) {
412
- log.warn('[onesub/google] Product receipt too old (>72h)');
413
- return null;
414
- }
415
- }
416
- if (!purchase.orderId) {
417
- log.warn('[onesub/google] No orderId in product purchase');
418
- return null;
419
- }
420
- return {
421
- transactionId: purchase.orderId,
422
- purchasedAt: purchase.purchaseTimeMillis
423
- ? new Date(parseInt(purchase.purchaseTimeMillis, 10)).toISOString()
424
- : new Date().toISOString(),
425
- };
426
- }
427
- /**
428
- * Decode a Google RTDN Pub/Sub notification.
429
- * Returns the extracted subscription notification data, or null for test pings
430
- * and notification kinds handled by other decoders (e.g. voided purchases).
431
- */
432
- export function decodeGoogleNotification(payload) {
433
- let notification;
434
- try {
435
- const json = Buffer.from(payload.message.data, 'base64').toString('utf-8');
436
- notification = JSON.parse(json);
437
- }
438
- catch {
439
- return null;
440
- }
441
- if (!notification.subscriptionNotification) {
442
- // test notification, voided purchase, or unsupported type
443
- return null;
444
- }
445
- const { notificationType, purchaseToken, subscriptionId } = notification.subscriptionNotification;
446
- return {
447
- notificationType,
448
- purchaseToken,
449
- subscriptionId,
450
- packageName: notification.packageName,
451
- };
452
- }
453
- /**
454
- * Decode a Google RTDN voidedPurchaseNotification, if the payload is one.
455
- * Returns null when the payload is a different notification kind.
456
- */
457
- export function decodeGoogleVoidedNotification(payload) {
458
- let notification;
459
- try {
460
- const json = Buffer.from(payload.message.data, 'base64').toString('utf-8');
461
- notification = JSON.parse(json);
462
- }
463
- catch {
464
- return null;
465
- }
466
- if (!notification.voidedPurchaseNotification)
467
- return null;
468
- const { purchaseToken, orderId, productType, refundType } = notification.voidedPurchaseNotification;
469
- return {
470
- purchaseToken,
471
- orderId,
472
- productType,
473
- refundType,
474
- packageName: notification.packageName,
475
- };
476
- }
477
- /**
478
- * Determine if a Google RTDN notification type represents an active subscription
479
- * inside the paid window (excludes grace period — that's a separate state now).
480
- */
481
- export function isGoogleActiveNotification(notificationType) {
482
- return (notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_PURCHASED ||
483
- notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_RENEWED ||
484
- notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_RECOVERED ||
485
- notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_RESTARTED);
486
- }
487
- export function isGoogleCanceledNotification(notificationType) {
488
- return (notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_CANCELED ||
489
- notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_REVOKED);
490
- }
491
- export function isGoogleExpiredNotification(notificationType) {
492
- return notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_EXPIRED;
493
- }
494
- /**
495
- * Subscription is in the store-granted grace period — payment failed but the
496
- * user retains access while Google retries. Maps to SUBSCRIPTION_STATUS.GRACE_PERIOD.
497
- */
498
- export function isGoogleGracePeriodNotification(notificationType) {
499
- return notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_IN_GRACE_PERIOD;
500
- }
501
- /**
502
- * Subscription is on hold — grace period ended, retry continuing, entitlement
503
- * REVOKED. Maps to SUBSCRIPTION_STATUS.ON_HOLD.
504
- */
505
- export function isGoogleOnHoldNotification(notificationType) {
506
- return notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_ON_HOLD;
507
- }
508
- /**
509
- * User-voluntary pause — entitlement REVOKED until autoResumeTime or manual
510
- * resume. Maps to SUBSCRIPTION_STATUS.PAUSED.
511
- */
512
- export function isGooglePausedNotification(notificationType) {
513
- return notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_PAUSED;
514
- }
515
- /**
516
- * User agreed to a developer-initiated price change. The new price applies
517
- * on the next renewal. Subscription remains active in the meantime.
518
- */
519
- export function isGooglePriceChangeConfirmedNotification(notificationType) {
520
- return notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_PRICE_CHANGE_CONFIRMED;
521
- }
522
- //# sourceMappingURL=google.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"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"}
@@ -1,93 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { SUBSCRIPTION_STATUS, MOCK_RECEIPT_PREFIX } from '@onesub/shared';
3
- import { log } from '../logger.js';
4
- export function classifyMockReceipt(receipt) {
5
- if (receipt.startsWith(MOCK_RECEIPT_PREFIX.NETWORK_ERROR))
6
- return { kind: 'network-error' };
7
- if (receipt.startsWith(MOCK_RECEIPT_PREFIX.REVOKED))
8
- return { kind: 'revoked' };
9
- if (receipt.startsWith(MOCK_RECEIPT_PREFIX.EXPIRED))
10
- return { kind: 'expired' };
11
- if (receipt.startsWith(MOCK_RECEIPT_PREFIX.INVALID) ||
12
- receipt.startsWith(MOCK_RECEIPT_PREFIX.BAD_SIG)) {
13
- return { kind: 'invalid' };
14
- }
15
- if (receipt.startsWith(MOCK_RECEIPT_PREFIX.SANDBOX))
16
- return { kind: 'sandbox' };
17
- return { kind: 'valid' };
18
- }
19
- /**
20
- * Gate the outcome against `valid` / `sandbox`. Throws for network-error,
21
- * logs + returns false for rejection cases, returns true for pass-through.
22
- * Every mock validator uses this so their rejection behavior stays in sync.
23
- */
24
- function outcomePasses(outcome, tag) {
25
- if (outcome.kind === 'network-error') {
26
- throw new Error(`[onesub/mock/${tag}] simulated upstream network error`);
27
- }
28
- if (outcome.kind === 'valid' || outcome.kind === 'sandbox')
29
- return true;
30
- log.warn(`[onesub/mock/${tag}] receipt rejected`, { outcome: outcome.kind });
31
- return false;
32
- }
33
- function deterministicTransactionId(prefix, receipt) {
34
- const digest = createHash('sha256').update(receipt).digest('hex').slice(0, 24);
35
- return `${prefix}_${digest}`;
36
- }
37
- const HOURS = 60 * 60 * 1000;
38
- const DAYS = 24 * HOURS;
39
- export function mockValidateAppleSubscription(receipt) {
40
- const outcome = classifyMockReceipt(receipt);
41
- if (!outcomePasses(outcome, 'apple'))
42
- return null;
43
- const now = Date.now();
44
- const expiresAt = outcome.kind === 'sandbox' ? now + 1 * HOURS : now + 30 * DAYS;
45
- return {
46
- userId: '',
47
- productId: 'mock_subscription',
48
- platform: 'apple',
49
- status: SUBSCRIPTION_STATUS.ACTIVE,
50
- expiresAt: new Date(expiresAt).toISOString(),
51
- originalTransactionId: deterministicTransactionId('mock_apple_orig', receipt),
52
- purchasedAt: new Date(now).toISOString(),
53
- willRenew: true,
54
- };
55
- }
56
- export function mockValidateAppleProduct(receipt, expectedProductId) {
57
- const outcome = classifyMockReceipt(receipt);
58
- if (!outcomePasses(outcome, 'apple'))
59
- return null;
60
- const productId = expectedProductId ?? 'mock_product';
61
- return {
62
- transactionId: deterministicTransactionId(`mock_apple_${productId}`, receipt),
63
- productId,
64
- purchasedAt: new Date().toISOString(),
65
- };
66
- }
67
- export function mockValidateGoogleSubscription(receipt, productId) {
68
- const outcome = classifyMockReceipt(receipt);
69
- if (!outcomePasses(outcome, 'google'))
70
- return null;
71
- const now = Date.now();
72
- const expiresAt = outcome.kind === 'sandbox' ? now + 1 * HOURS : now + 30 * DAYS;
73
- return {
74
- userId: '',
75
- productId,
76
- platform: 'google',
77
- status: SUBSCRIPTION_STATUS.ACTIVE,
78
- expiresAt: new Date(expiresAt).toISOString(),
79
- originalTransactionId: deterministicTransactionId('mock_google_orig', receipt),
80
- purchasedAt: new Date(now).toISOString(),
81
- willRenew: true,
82
- };
83
- }
84
- export function mockValidateGoogleProduct(receipt, productId) {
85
- const outcome = classifyMockReceipt(receipt);
86
- if (!outcomePasses(outcome, 'google'))
87
- return null;
88
- return {
89
- transactionId: deterministicTransactionId(`mock_google_${productId}`, receipt),
90
- purchasedAt: new Date().toISOString(),
91
- };
92
- }
93
- //# sourceMappingURL=mock.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mock.js","sourceRoot":"","sources":["../../src/providers/mock.ts"],"names":[],"mappings":"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"}