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