@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.
- package/README.md +119 -2
- package/dist/__tests__/cache.test.d.ts +2 -0
- package/dist/__tests__/cache.test.d.ts.map +1 -0
- package/dist/__tests__/openapi.test.d.ts +2 -0
- package/dist/__tests__/openapi.test.d.ts.map +1 -0
- package/dist/__tests__/redis-store.test.d.ts +2 -0
- package/dist/__tests__/redis-store.test.d.ts.map +1 -0
- package/dist/__tests__/webhook-apple.test.d.ts +12 -0
- package/dist/__tests__/webhook-apple.test.d.ts.map +1 -0
- package/dist/__tests__/webhook-events.test.d.ts +2 -0
- package/dist/__tests__/webhook-events.test.d.ts.map +1 -0
- package/dist/__tests__/webhook-google.test.d.ts +13 -0
- package/dist/__tests__/webhook-google.test.d.ts.map +1 -0
- package/dist/__tests__/webhook-queue.test.d.ts +2 -0
- package/dist/__tests__/webhook-queue.test.d.ts.map +1 -0
- package/dist/cache.d.ts +32 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/index.cjs +3084 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +34 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3045 -118
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +41 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/apple.d.ts.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/routes/admin.d.ts +2 -1
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/webhook-apple.d.ts +6 -0
- package/dist/routes/webhook-apple.d.ts.map +1 -0
- package/dist/routes/webhook-google.d.ts +7 -0
- package/dist/routes/webhook-google.d.ts.map +1 -0
- package/dist/routes/webhook.d.ts +6 -18
- package/dist/routes/webhook.d.ts.map +1 -1
- package/dist/stores/redis.d.ts +60 -0
- package/dist/stores/redis.d.ts.map +1 -0
- package/dist/stores/schema.d.ts.map +1 -1
- package/dist/tracing.d.ts +32 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/webhook-events.d.ts +58 -0
- package/dist/webhook-events.d.ts.map +1 -0
- package/dist/webhook-queue.d.ts +92 -0
- package/dist/webhook-queue.d.ts.map +1 -0
- package/package.json +33 -4
- package/sql/schema.sql +10 -0
- package/dist/__tests__/admin-customer-detail.test.js +0 -146
- package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
- package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
- package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
- package/dist/__tests__/admin-subscription-detail.test.js +0 -112
- package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
- package/dist/__tests__/apple-root-ca.test.js +0 -41
- package/dist/__tests__/apple-root-ca.test.js.map +0 -1
- package/dist/__tests__/apple-status-api.test.js +0 -345
- package/dist/__tests__/apple-status-api.test.js.map +0 -1
- package/dist/__tests__/entitlements.test.js +0 -268
- package/dist/__tests__/entitlements.test.js.map +0 -1
- package/dist/__tests__/error-codes.test.js +0 -168
- package/dist/__tests__/error-codes.test.js.map +0 -1
- package/dist/__tests__/fetch-hardening.test.js +0 -192
- package/dist/__tests__/fetch-hardening.test.js.map +0 -1
- package/dist/__tests__/google-paused.test.js +0 -126
- package/dist/__tests__/google-paused.test.js.map +0 -1
- package/dist/__tests__/google-price-change.test.js +0 -186
- package/dist/__tests__/google-price-change.test.js.map +0 -1
- package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
- package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
- package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
- package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
- package/dist/__tests__/lifecycle-states.test.js +0 -423
- package/dist/__tests__/lifecycle-states.test.js.map +0 -1
- package/dist/__tests__/linked-purchase-token.test.js +0 -227
- package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
- package/dist/__tests__/metrics.test.js +0 -340
- package/dist/__tests__/metrics.test.js.map +0 -1
- package/dist/__tests__/mock-provider.test.js +0 -188
- package/dist/__tests__/mock-provider.test.js.map +0 -1
- package/dist/__tests__/paused-auto-resume.test.js +0 -162
- package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
- package/dist/__tests__/providers.test.js +0 -321
- package/dist/__tests__/providers.test.js.map +0 -1
- package/dist/__tests__/refund-policy.test.js +0 -317
- package/dist/__tests__/refund-policy.test.js.map +0 -1
- package/dist/__tests__/schema.test.js +0 -46
- package/dist/__tests__/schema.test.js.map +0 -1
- package/dist/__tests__/store.test.js +0 -172
- package/dist/__tests__/store.test.js.map +0 -1
- package/dist/__tests__/test-utils.js +0 -30
- package/dist/__tests__/test-utils.js.map +0 -1
- package/dist/__tests__/webhook-refund.test.js +0 -313
- package/dist/__tests__/webhook-refund.test.js.map +0 -1
- package/dist/errors.js +0 -19
- package/dist/errors.js.map +0 -1
- package/dist/http.js +0 -36
- package/dist/http.js.map +0 -1
- package/dist/logger.js +0 -20
- package/dist/logger.js.map +0 -1
- package/dist/providers/apple-root-ca.js +0 -38
- package/dist/providers/apple-root-ca.js.map +0 -1
- package/dist/providers/apple.js +0 -499
- package/dist/providers/apple.js.map +0 -1
- package/dist/providers/google.js +0 -493
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/mock.js +0 -93
- package/dist/providers/mock.js.map +0 -1
- package/dist/routes/admin.js +0 -251
- package/dist/routes/admin.js.map +0 -1
- package/dist/routes/entitlements.js +0 -130
- package/dist/routes/entitlements.js.map +0 -1
- package/dist/routes/metrics.js +0 -281
- package/dist/routes/metrics.js.map +0 -1
- package/dist/routes/purchase.js +0 -187
- package/dist/routes/purchase.js.map +0 -1
- package/dist/routes/status.js +0 -55
- package/dist/routes/status.js.map +0 -1
- package/dist/routes/validate.js +0 -70
- package/dist/routes/validate.js.map +0 -1
- package/dist/routes/webhook.js +0 -441
- package/dist/routes/webhook.js.map +0 -1
- package/dist/store.js +0 -145
- package/dist/store.js.map +0 -1
- package/dist/stores/postgres.js +0 -329
- package/dist/stores/postgres.js.map +0 -1
- package/dist/stores/schema.js +0 -57
- package/dist/stores/schema.js.map +0 -1
package/dist/routes/webhook.js
DELETED
|
@@ -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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"}
|