@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,441 +0,0 @@
1
- import { Router } from 'express';
2
- import { createRemoteJWKSet, jwtVerify } from 'jose';
3
- import { ROUTES, SUBSCRIPTION_STATUS, ONESUB_ERROR_CODE } from '@onesub/shared';
4
- import { decodeAppleNotification, decodeJws, sendAppleConsumptionResponse, fetchAppleSubscriptionStatus, } from '../providers/apple.js';
5
- import { decodeGoogleNotification, decodeGoogleVoidedNotification, validateGoogleReceipt, isGoogleActiveNotification, isGoogleCanceledNotification, isGoogleExpiredNotification, isGoogleGracePeriodNotification, isGoogleOnHoldNotification, isGooglePausedNotification, isGooglePriceChangeConfirmedNotification, } from '../providers/google.js';
6
- import { log } from '../logger.js';
7
- import { sendError } from '../errors.js';
8
- /**
9
- * Google's public JWKS endpoint used to verify Pub/Sub push JWT tokens.
10
- */
11
- const GOOGLE_JWKS_URL = 'https://www.googleapis.com/oauth2/v3/certs';
12
- // Lazily initialised — the JWKS fetch only occurs when the endpoint is first hit
13
- // and pushAudience is configured.
14
- let googleJwks = null;
15
- function getGoogleJwks() {
16
- if (!googleJwks) {
17
- googleJwks = createRemoteJWKSet(new URL(GOOGLE_JWKS_URL));
18
- }
19
- return googleJwks;
20
- }
21
- /**
22
- * Verifies the `Authorization: Bearer <token>` header as a Google-signed JWT
23
- * and checks that the `aud` claim matches `expectedAudience`.
24
- *
25
- * Returns `true` when verification succeeds, `false` otherwise.
26
- */
27
- async function verifyGooglePushToken(req, expectedAudience) {
28
- const authHeader = req.headers['authorization'];
29
- if (typeof authHeader !== 'string' || !authHeader.startsWith('Bearer ')) {
30
- return false;
31
- }
32
- const token = authHeader.slice('Bearer '.length).trim();
33
- try {
34
- await jwtVerify(token, getGoogleJwks(), { audience: expectedAudience });
35
- return true;
36
- }
37
- catch {
38
- return false;
39
- }
40
- }
41
- /**
42
- * Apple V2 notification types that indicate the subscription is now active.
43
- */
44
- const APPLE_ACTIVE_TYPES = new Set([
45
- 'SUBSCRIBED',
46
- 'DID_RENEW',
47
- 'DID_RECOVER',
48
- 'OFFER_REDEEMED',
49
- ]);
50
- /**
51
- * Apple V2 notification types that indicate cancellation.
52
- */
53
- const APPLE_CANCELED_TYPES = new Set([
54
- 'REVOKE',
55
- 'REFUND',
56
- 'CONSUMPTION_REQUEST',
57
- ]);
58
- /**
59
- * Apple V2 notification types that indicate expiry.
60
- * Pure EXPIRED only — GRACE_PERIOD_EXPIRED is handled separately as on_hold
61
- * because billing retry continues after the grace window ends.
62
- */
63
- const APPLE_EXPIRED_TYPES = new Set([
64
- 'EXPIRED',
65
- ]);
66
- /**
67
- * Map an Apple notification (notificationType + optional subtype) to a
68
- * lifecycle state. Returns null if the notification doesn't carry an explicit
69
- * lifecycle signal (in which case the caller falls back to the JWS-derived status).
70
- *
71
- * Apple references:
72
- * DID_FAIL_TO_RENEW + subtype GRACE_PERIOD → GRACE_PERIOD
73
- * DID_FAIL_TO_RENEW (no subtype) → ON_HOLD (billing retry, no grace)
74
- * GRACE_PERIOD_EXPIRED → ON_HOLD (grace ended, retry continues)
75
- * EXPIRED → EXPIRED (terminal, no further retries)
76
- * SUBSCRIBED / DID_RENEW / DID_RECOVER /
77
- * OFFER_REDEEMED → ACTIVE
78
- * REVOKE / REFUND / CONSUMPTION_REQUEST → CANCELED
79
- */
80
- function mapAppleNotificationStatus(notificationType, subtype) {
81
- if (notificationType === 'DID_FAIL_TO_RENEW') {
82
- return subtype === 'GRACE_PERIOD'
83
- ? SUBSCRIPTION_STATUS.GRACE_PERIOD
84
- : SUBSCRIPTION_STATUS.ON_HOLD;
85
- }
86
- if (notificationType === 'GRACE_PERIOD_EXPIRED')
87
- return SUBSCRIPTION_STATUS.ON_HOLD;
88
- if (APPLE_CANCELED_TYPES.has(notificationType))
89
- return SUBSCRIPTION_STATUS.CANCELED;
90
- if (APPLE_EXPIRED_TYPES.has(notificationType))
91
- return SUBSCRIPTION_STATUS.EXPIRED;
92
- if (APPLE_ACTIVE_TYPES.has(notificationType))
93
- return SUBSCRIPTION_STATUS.ACTIVE;
94
- return null;
95
- }
96
- /**
97
- * Retry / durability semantics.
98
- *
99
- * Both Apple (App Store Server Notifications V2) and Google (Pub/Sub RTDN)
100
- * retry on any non-2xx response:
101
- * - Apple: up to 5 retries over ~3 days, exponential backoff.
102
- * - Google: per-Pub/Sub-subscription retry policy (default: retries until
103
- * the message is ack'd or the retention window expires, up to 7 days).
104
- *
105
- * This router uses that built-in retry instead of a local dead-letter queue:
106
- *
107
- * 4xx — the payload is unusable (missing signedPayload, bad signature,
108
- * package mismatch). Return 4xx so the sender does NOT retry, since
109
- * the same request would fail again.
110
- * 5xx — a transient failure on our side (DB down, network to Play API).
111
- * Return 5xx so Apple / Google retry the notification for us.
112
- * 2xx — processed, or intentionally ignored (e.g. test notification).
113
- *
114
- * If you need an explicit DLQ, wrap `store.save()` with your own error
115
- * handler before passing the store to `createOneSubMiddleware()`.
116
- */
117
- export function createWebhookRouter(config, store, purchaseStore) {
118
- const router = Router();
119
- /**
120
- * POST /onesub/webhook/apple
121
- *
122
- * Receives Apple App Store Server Notifications V2.
123
- * Apple sends a JWS-signed payload with a `signedPayload` field.
124
- */
125
- router.post(ROUTES.WEBHOOK_APPLE, async (req, res) => {
126
- // Apple sends the notification as { signedPayload: "<JWS>" }.
127
- // Only the JWS-signed path is accepted. Pre-decoded payloads are rejected
128
- // because they bypass signature verification and allow arbitrary state changes.
129
- const body = req.body;
130
- if (!body.signedPayload) {
131
- sendError(res, 400, ONESUB_ERROR_CODE.MISSING_SIGNED_PAYLOAD, 'Missing signedPayload');
132
- return;
133
- }
134
- let payload;
135
- try {
136
- payload = await decodeJws(body.signedPayload, config.apple?.skipJwsVerification);
137
- }
138
- catch (err) {
139
- log.error('[onesub/webhook/apple] Failed to decode signedPayload:', err);
140
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_SIGNED_PAYLOAD, 'Invalid signedPayload');
141
- return;
142
- }
143
- const decoded = await decodeAppleNotification(payload, config.apple?.skipJwsVerification);
144
- if (!decoded) {
145
- // Could be a test notification or unsupported type — acknowledge it
146
- res.status(200).json({ received: true });
147
- return;
148
- }
149
- const { originalTransactionId, transactionId, type, productId, bundleId, environment, status, willRenew, expiresAt, } = decoded;
150
- const notificationType = payload.notificationType;
151
- const subtype = payload.subtype;
152
- // Derive final status from the notification type + subtype (overrides the
153
- // JWS-derived status when there is an explicit lifecycle signal).
154
- const mapped = mapAppleNotificationStatus(notificationType, subtype);
155
- const finalStatus = mapped ?? status;
156
- // CONSUMPTION_REQUEST — Apple is asking whether to grant a consumable
157
- // refund. If the host app provided a consumptionInfoProvider, call it and
158
- // PUT the response. Failures are logged; the webhook still 200s.
159
- if (notificationType === 'CONSUMPTION_REQUEST' &&
160
- config.apple?.consumptionInfoProvider &&
161
- transactionId &&
162
- productId) {
163
- const provider = config.apple.consumptionInfoProvider;
164
- const appleConfig = config.apple;
165
- void (async () => {
166
- try {
167
- const info = await provider({
168
- transactionId,
169
- originalTransactionId,
170
- productId,
171
- bundleId: bundleId ?? appleConfig.bundleId,
172
- environment,
173
- });
174
- if (info) {
175
- await sendAppleConsumptionResponse(transactionId, info, appleConfig, {
176
- sandbox: environment === 'Sandbox',
177
- });
178
- }
179
- }
180
- catch (err) {
181
- log.warn('[onesub/webhook/apple] consumptionInfoProvider failed:', err);
182
- }
183
- })();
184
- }
185
- // IAP refund / revoke for one-time purchases (consumable / non-consumable).
186
- // Apple sends REFUND notifications for both subscriptions and IAP — the
187
- // transaction `type` field disambiguates. Subscription refunds keep flowing
188
- // into the SubscriptionStore branch below.
189
- const isOneTimePurchase = type === 'Consumable' || type === 'Non-Consumable';
190
- const isRefundOrRevoke = APPLE_CANCELED_TYPES.has(notificationType);
191
- try {
192
- if (isOneTimePurchase && isRefundOrRevoke) {
193
- // For consumables, the refunded transactionId is unique per purchase.
194
- // For non-consumables, transactionId === originalTransactionId, so either
195
- // lookup succeeds.
196
- const lookupId = transactionId ?? originalTransactionId;
197
- const removed = await purchaseStore.deletePurchaseByTransactionId(lookupId);
198
- if (!removed) {
199
- log.warn('[onesub/webhook/apple] IAP refund for unknown transaction:', lookupId);
200
- }
201
- res.status(200).json({ received: true });
202
- return;
203
- }
204
- const existing = await store.getByTransactionId(originalTransactionId);
205
- if (existing) {
206
- // refundPolicy='until_expiry' for subscription refunds: keep status +
207
- // expiresAt untouched, only flip willRenew=false. The status route's
208
- // stale-record check will drop the user automatically once expiresAt
209
- // passes. Refer to OneSubServerConfig.refundPolicy for rationale.
210
- const isSubscriptionRefund = isRefundOrRevoke && !isOneTimePurchase && notificationType !== 'CONSUMPTION_REQUEST';
211
- const keepEntitlement = isSubscriptionRefund && config.refundPolicy === 'until_expiry';
212
- const updated = keepEntitlement
213
- ? { ...existing, willRenew: false }
214
- : {
215
- ...existing,
216
- status: finalStatus,
217
- willRenew,
218
- // expiresAt may be absent on non-subscription payloads — keep the
219
- // previously-stored value rather than overwriting with null/empty.
220
- expiresAt: expiresAt ?? existing.expiresAt,
221
- };
222
- await store.save(updated);
223
- }
224
- else if (config.apple?.issuerId && config.apple?.keyId && config.apple?.privateKey) {
225
- // No local record but App Store Server API credentials are present —
226
- // fetch the canonical state from Apple. This recovers from missed
227
- // webhooks (server downtime, queue truncation) and bootstraps
228
- // subscriptions purchased before this server existed.
229
- // userId is unknown at webhook time — store under originalTransactionId
230
- // as placeholder so a later /onesub/validate call can claim ownership.
231
- const fresh = await fetchAppleSubscriptionStatus(originalTransactionId, config.apple, {
232
- sandbox: environment === 'Sandbox',
233
- });
234
- if (fresh) {
235
- fresh.userId = originalTransactionId;
236
- await store.save(fresh);
237
- }
238
- else {
239
- log.warn('[onesub/webhook/apple] Unknown transaction and Status API returned no record:', originalTransactionId);
240
- }
241
- }
242
- else {
243
- // No local record and no API credentials to re-fetch — log and ack.
244
- log.warn('[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover):', originalTransactionId);
245
- }
246
- res.status(200).json({ received: true });
247
- }
248
- catch (err) {
249
- log.error('[onesub/webhook/apple] Store update error:', err);
250
- sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, 'Failed to update subscription');
251
- }
252
- });
253
- /**
254
- * POST /onesub/webhook/google
255
- *
256
- * Receives Google Play Real-Time Developer Notifications (RTDN) via Pub/Sub push.
257
- * The body is a standard Pub/Sub push message with base64-encoded data.
258
- */
259
- router.post(ROUTES.WEBHOOK_GOOGLE, async (req, res) => {
260
- // Verify Google-signed JWT when pushAudience is configured.
261
- // If pushAudience is not set, authentication is skipped for backward compatibility.
262
- if (config.google?.pushAudience) {
263
- const authenticated = await verifyGooglePushToken(req, config.google.pushAudience);
264
- if (!authenticated) {
265
- sendError(res, 401, ONESUB_ERROR_CODE.UNAUTHORIZED, 'Unauthorized');
266
- return;
267
- }
268
- }
269
- const body = req.body;
270
- if (!body.message?.data) {
271
- sendError(res, 400, ONESUB_ERROR_CODE.MISSING_MESSAGE_DATA, 'Missing message.data');
272
- return;
273
- }
274
- // voidedPurchaseNotification — Google's refund/chargeback signal for both
275
- // subscriptions and one-time products. Routed here before the regular
276
- // subscriptionNotification decoder so it doesn't get swallowed as "unknown".
277
- const voided = decodeGoogleVoidedNotification(body);
278
- if (voided) {
279
- if (config.google?.packageName && voided.packageName !== config.google.packageName) {
280
- log.warn('[onesub/webhook/google] voided package name mismatch:', voided.packageName, '!==', config.google.packageName);
281
- sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, 'Package name mismatch');
282
- return;
283
- }
284
- try {
285
- if (voided.productType === 1) {
286
- // Subscription refund — purchaseToken is stored as originalTransactionId.
287
- const existing = await store.getByTransactionId(voided.purchaseToken);
288
- if (existing) {
289
- // refundPolicy='until_expiry' for subscription refunds: keep
290
- // status + expiresAt, only flip willRenew. See OneSubServerConfig.
291
- const updated = config.refundPolicy === 'until_expiry'
292
- ? { ...existing, willRenew: false }
293
- : { ...existing, status: SUBSCRIPTION_STATUS.CANCELED };
294
- await store.save(updated);
295
- }
296
- else {
297
- log.warn('[onesub/webhook/google] voided subscription for unknown purchaseToken:', voided.purchaseToken);
298
- }
299
- }
300
- else {
301
- // One-time product refund — orderId is stored as transactionId.
302
- // Always immediate (refundPolicy doesn't apply — IAP has no expiry).
303
- const removed = await purchaseStore.deletePurchaseByTransactionId(voided.orderId);
304
- if (!removed) {
305
- log.warn('[onesub/webhook/google] voided IAP for unknown orderId:', voided.orderId);
306
- }
307
- }
308
- res.status(200).json({ received: true });
309
- }
310
- catch (err) {
311
- log.error('[onesub/webhook/google] voided notification error:', err);
312
- sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, 'Failed to process voided notification');
313
- }
314
- return;
315
- }
316
- const notification = decodeGoogleNotification(body);
317
- if (!notification) {
318
- // Test notification or unknown type — acknowledge
319
- res.status(200).json({ received: true });
320
- return;
321
- }
322
- const { notificationType, purchaseToken, subscriptionId, packageName } = notification;
323
- // Validate the package name matches our config
324
- if (config.google?.packageName && packageName !== config.google.packageName) {
325
- log.warn('[onesub/webhook/google] Package name mismatch:', packageName, '!==', config.google.packageName);
326
- sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, 'Package name mismatch');
327
- return;
328
- }
329
- try {
330
- // Try to find the existing record by purchase token (used as originalTransactionId)
331
- const existing = await store.getByTransactionId(purchaseToken);
332
- let finalStatus;
333
- if (isGoogleActiveNotification(notificationType)) {
334
- finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
335
- }
336
- else if (isGoogleGracePeriodNotification(notificationType)) {
337
- finalStatus = SUBSCRIPTION_STATUS.GRACE_PERIOD;
338
- }
339
- else if (isGoogleOnHoldNotification(notificationType)) {
340
- finalStatus = SUBSCRIPTION_STATUS.ON_HOLD;
341
- }
342
- else if (isGooglePausedNotification(notificationType)) {
343
- finalStatus = SUBSCRIPTION_STATUS.PAUSED;
344
- }
345
- else if (isGooglePriceChangeConfirmedNotification(notificationType)) {
346
- // User accepted a developer-initiated price change. Subscription stays
347
- // active; the new price kicks in at the next renewal. Host can hook
348
- // onPriceChangeConfirmed below to log/notify.
349
- finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
350
- }
351
- else if (isGoogleCanceledNotification(notificationType)) {
352
- finalStatus = SUBSCRIPTION_STATUS.CANCELED;
353
- }
354
- else if (isGoogleExpiredNotification(notificationType)) {
355
- finalStatus = SUBSCRIPTION_STATUS.EXPIRED;
356
- }
357
- else {
358
- // Unhandled notification type (PAUSE_SCHEDULE_CHANGED, DEFERRED, etc.) —
359
- // re-fetch from Play API will correct the status (subscriptionsv2 returns
360
- // SUBSCRIPTION_STATE_PAUSED etc. directly).
361
- finalStatus = SUBSCRIPTION_STATUS.ACTIVE; // optimistic; re-fetch below will correct it
362
- }
363
- // Fire-and-forget hook for PRICE_CHANGE_CONFIRMED — happens before store
364
- // save so the hook still runs even if save errors out (the host's
365
- // analytics/notification side effect shouldn't block on DB issues).
366
- if (isGooglePriceChangeConfirmedNotification(notificationType) &&
367
- config.google?.onPriceChangeConfirmed) {
368
- const hook = config.google.onPriceChangeConfirmed;
369
- void Promise.resolve()
370
- .then(() => hook({ purchaseToken, subscriptionId, packageName }))
371
- .catch((err) => log.warn('[onesub/webhook/google] onPriceChangeConfirmed hook failed:', err));
372
- }
373
- if (existing) {
374
- // Re-validate against Play API to get the latest expiry date
375
- let updated = { ...existing, status: finalStatus };
376
- if (config.google?.serviceAccountKey) {
377
- const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
378
- if (fresh) {
379
- // Notification-derived grace_period/on_hold are authoritative — the
380
- // expiry-based deriveStatus() in the provider can't observe these
381
- // states (paymentState heuristics are unreliable), so trust the
382
- // RTDN signal over the API-derived status for those two cases.
383
- const preserveNotificationStatus = finalStatus === SUBSCRIPTION_STATUS.GRACE_PERIOD ||
384
- finalStatus === SUBSCRIPTION_STATUS.ON_HOLD;
385
- updated = {
386
- ...existing,
387
- status: preserveNotificationStatus ? finalStatus : fresh.status,
388
- expiresAt: fresh.expiresAt,
389
- willRenew: fresh.willRenew,
390
- // Propagate v2-only fields that fresh may have updated. Without
391
- // these, paused→active transitions left autoResumeTime stale and
392
- // upgrade chains lost their linkedPurchaseToken on subsequent
393
- // notifications. Use ?? undefined so cleared values become undefined
394
- // (e.g. resume from paused → autoResumeTime should disappear).
395
- autoResumeTime: fresh.autoResumeTime,
396
- linkedPurchaseToken: fresh.linkedPurchaseToken ?? existing.linkedPurchaseToken,
397
- };
398
- }
399
- }
400
- await store.save(updated);
401
- }
402
- else {
403
- // Unknown purchase token — attempt to create a record via Play API
404
- if (config.google?.serviceAccountKey) {
405
- const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
406
- if (fresh) {
407
- // Upgrade/downgrade continuity: if the v2 response carries a
408
- // linkedPurchaseToken pointing at a previous subscription we know
409
- // about, inherit the userId from it so the same person doesn't
410
- // appear as a brand-new placeholder after a plan change.
411
- if (fresh.linkedPurchaseToken) {
412
- const previous = await store.getByTransactionId(fresh.linkedPurchaseToken);
413
- if (previous) {
414
- fresh.userId = previous.userId;
415
- log.info(`[onesub/webhook/google] inherited userId ${previous.userId} from linkedPurchaseToken ${fresh.linkedPurchaseToken} → new token ${purchaseToken}`);
416
- }
417
- else {
418
- fresh.userId = purchaseToken;
419
- }
420
- }
421
- else {
422
- // userId is unknown at webhook time — use purchaseToken as placeholder
423
- fresh.userId = purchaseToken;
424
- }
425
- await store.save(fresh);
426
- }
427
- }
428
- else {
429
- log.warn('[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch:', purchaseToken);
430
- }
431
- }
432
- res.status(200).json({ received: true });
433
- }
434
- catch (err) {
435
- log.error('[onesub/webhook/google] Error handling notification:', err);
436
- sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, 'Failed to process notification');
437
- }
438
- });
439
- return router;
440
- }
441
- //# sourceMappingURL=webhook.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"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"}
package/dist/store.js DELETED
@@ -1,145 +0,0 @@
1
- /**
2
- * In-memory implementation — suitable for development and testing.
3
- * Data is lost on process restart.
4
- */
5
- export class InMemorySubscriptionStore {
6
- // Multiple records per user (different originalTransactionIds) — required
7
- // for entitlement evaluation across multiple productIds. Ordering is
8
- // last-written-first so getByUserId returns "most recent" naturally.
9
- byUserId = new Map();
10
- byTransactionId = new Map();
11
- async save(sub) {
12
- this.byTransactionId.set(sub.originalTransactionId, sub);
13
- const existing = this.byUserId.get(sub.userId) ?? [];
14
- // Replace any prior record with the same originalTransactionId, then
15
- // unshift to the front so the latest write is index 0 (= getByUserId result).
16
- const filtered = existing.filter((s) => s.originalTransactionId !== sub.originalTransactionId);
17
- filtered.unshift(sub);
18
- this.byUserId.set(sub.userId, filtered);
19
- }
20
- async getByUserId(userId) {
21
- const list = this.byUserId.get(userId);
22
- return list?.[0] ?? null;
23
- }
24
- async getAllByUserId(userId) {
25
- return [...(this.byUserId.get(userId) ?? [])];
26
- }
27
- async getByTransactionId(txId) {
28
- return this.byTransactionId.get(txId) ?? null;
29
- }
30
- async listAll() {
31
- return [...this.byTransactionId.values()];
32
- }
33
- async listFiltered(opts) {
34
- const limit = opts.limit ?? 50;
35
- const offset = opts.offset ?? 0;
36
- // Iterate via byUserId so insertion-order is "newest first" within each
37
- // user (matches PostgresStore's `updated_at DESC` semantic). Across users
38
- // we collect in Map iteration order — stable for a given run.
39
- const all = [];
40
- for (const list of this.byUserId.values()) {
41
- for (const s of list)
42
- all.push(s);
43
- }
44
- const filtered = all.filter((s) => {
45
- if (opts.userId && s.userId !== opts.userId)
46
- return false;
47
- if (opts.status && s.status !== opts.status)
48
- return false;
49
- if (opts.productId && s.productId !== opts.productId)
50
- return false;
51
- if (opts.platform && s.platform !== opts.platform)
52
- return false;
53
- return true;
54
- });
55
- return {
56
- items: filtered.slice(offset, offset + limit),
57
- total: filtered.length,
58
- limit,
59
- offset,
60
- };
61
- }
62
- }
63
- /**
64
- * In-memory implementation of PurchaseStore — suitable for development and testing.
65
- * Data is lost on process restart.
66
- */
67
- export class InMemoryPurchaseStore {
68
- byTransactionId = new Map();
69
- byUserId = new Map();
70
- async savePurchase(purchase) {
71
- const existing = this.byTransactionId.get(purchase.transactionId);
72
- if (existing) {
73
- if (existing.userId !== purchase.userId) {
74
- const err = new Error('TRANSACTION_BELONGS_TO_OTHER_USER');
75
- err.code = 'TRANSACTION_BELONGS_TO_OTHER_USER';
76
- throw err;
77
- }
78
- return; // same user — idempotent
79
- }
80
- this.byTransactionId.set(purchase.transactionId, purchase);
81
- const list = this.byUserId.get(purchase.userId) ?? [];
82
- list.push(purchase);
83
- this.byUserId.set(purchase.userId, list);
84
- }
85
- async getPurchasesByUserId(userId) {
86
- return this.byUserId.get(userId) ?? [];
87
- }
88
- async getPurchaseByTransactionId(txId) {
89
- return this.byTransactionId.get(txId) ?? null;
90
- }
91
- async hasPurchased(userId, productId) {
92
- const purchases = this.byUserId.get(userId);
93
- if (!purchases)
94
- return false;
95
- return purchases.some((p) => p.productId === productId);
96
- }
97
- async reassignPurchase(transactionId, newUserId) {
98
- const existing = this.byTransactionId.get(transactionId);
99
- if (!existing)
100
- return false;
101
- const oldUserId = existing.userId;
102
- if (oldUserId === newUserId)
103
- return true;
104
- const updated = { ...existing, userId: newUserId };
105
- this.byTransactionId.set(transactionId, updated);
106
- // remove from old userId index
107
- const oldList = (this.byUserId.get(oldUserId) ?? []).filter((p) => p.transactionId !== transactionId);
108
- if (oldList.length)
109
- this.byUserId.set(oldUserId, oldList);
110
- else
111
- this.byUserId.delete(oldUserId);
112
- // add to new userId index
113
- const newList = this.byUserId.get(newUserId) ?? [];
114
- newList.push(updated);
115
- this.byUserId.set(newUserId, newList);
116
- return true;
117
- }
118
- async deletePurchases(userId, productId) {
119
- const list = this.byUserId.get(userId) ?? [];
120
- const kept = list.filter((p) => p.productId !== productId);
121
- const deleted = list.length - kept.length;
122
- this.byUserId.set(userId, kept);
123
- for (const p of list) {
124
- if (p.productId === productId)
125
- this.byTransactionId.delete(p.transactionId);
126
- }
127
- return deleted;
128
- }
129
- async listAll() {
130
- return [...this.byTransactionId.values()];
131
- }
132
- async deletePurchaseByTransactionId(transactionId) {
133
- const existing = this.byTransactionId.get(transactionId);
134
- if (!existing)
135
- return false;
136
- this.byTransactionId.delete(transactionId);
137
- const list = (this.byUserId.get(existing.userId) ?? []).filter((p) => p.transactionId !== transactionId);
138
- if (list.length)
139
- this.byUserId.set(existing.userId, list);
140
- else
141
- this.byUserId.delete(existing.userId);
142
- return true;
143
- }
144
- }
145
- //# sourceMappingURL=store.js.map
package/dist/store.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}