@onesub/server 0.1.0
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/dist/__tests__/store.test.d.ts +2 -0
- package/dist/__tests__/store.test.d.ts.map +1 -0
- package/dist/__tests__/store.test.js +71 -0
- package/dist/__tests__/store.test.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/apple.d.ts +29 -0
- package/dist/providers/apple.d.ts.map +1 -0
- package/dist/providers/apple.js +111 -0
- package/dist/providers/apple.js.map +1 -0
- package/dist/providers/google.d.ts +48 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +203 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/routes/status.d.ts +4 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +41 -0
- package/dist/routes/status.js.map +1 -0
- package/dist/routes/validate.d.ts +5 -0
- package/dist/routes/validate.d.ts.map +1 -0
- package/dist/routes/validate.js +91 -0
- package/dist/routes/validate.js.map +1 -0
- package/dist/routes/webhook.d.ts +5 -0
- package/dist/routes/webhook.d.ts.map +1 -0
- package/dist/routes/webhook.js +219 -0
- package/dist/routes/webhook.js.map +1 -0
- package/dist/store.d.ts +23 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +19 -0
- package/dist/store.js.map +1 -0
- package/dist/stores/postgres.d.ts +49 -0
- package/dist/stores/postgres.d.ts.map +1 -0
- package/dist/stores/postgres.js +138 -0
- package/dist/stores/postgres.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA2CnE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { ROUTES, SUBSCRIPTION_STATUS } from '@onesub/shared';
|
|
3
|
+
export function createStatusRouter(store) {
|
|
4
|
+
const router = Router();
|
|
5
|
+
/**
|
|
6
|
+
* GET /onesub/status?userId=xxx
|
|
7
|
+
*
|
|
8
|
+
* Returns whether the user has an active subscription and the full
|
|
9
|
+
* SubscriptionInfo if one exists.
|
|
10
|
+
*/
|
|
11
|
+
router.get(ROUTES.STATUS, async (req, res) => {
|
|
12
|
+
const userId = req.query['userId'];
|
|
13
|
+
if (!userId || typeof userId !== 'string') {
|
|
14
|
+
const response = { active: false, subscription: null };
|
|
15
|
+
res.status(400).json({ ...response, error: 'Missing required query param: userId' });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (userId.length > 256) {
|
|
19
|
+
const response = { active: false, subscription: null };
|
|
20
|
+
res.status(400).json({ ...response, error: 'userId must not exceed 256 characters' });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const sub = await store.getByUserId(userId);
|
|
25
|
+
if (!sub) {
|
|
26
|
+
const response = { active: false, subscription: null };
|
|
27
|
+
res.status(200).json(response);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const active = sub.status === SUBSCRIPTION_STATUS.ACTIVE;
|
|
31
|
+
const response = { active, subscription: sub };
|
|
32
|
+
res.status(200).json(response);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error('[onesub/status] Store error:', err);
|
|
36
|
+
res.status(500).json({ active: false, subscription: null, error: 'Internal server error' });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return router;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG7D,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACzD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QAC9D,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAEnC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1C,MAAM,QAAQ,GAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;YACvE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;YACvE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAC,CAAC;YACtF,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAE5C,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,MAAM,QAAQ,GAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;gBACvE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,KAAK,mBAAmB,CAAC,MAAM,CAAC;YACzD,MAAM,QAAQ,GAAmB,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;YAC/D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;YACnD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import type { OneSubServerConfig } from '@onesub/shared';
|
|
3
|
+
import type { SubscriptionStore } from '../store.js';
|
|
4
|
+
export declare function createValidateRouter(config: OneSubServerConfig, store: SubscriptionStore): Router;
|
|
5
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/routes/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAA2B,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAElF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAWrD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAoFR"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ROUTES } from '@onesub/shared';
|
|
4
|
+
import { validateAppleReceipt } from '../providers/apple.js';
|
|
5
|
+
import { validateGoogleReceipt } from '../providers/google.js';
|
|
6
|
+
const validateSchema = z.object({
|
|
7
|
+
platform: z.enum(['apple', 'google']),
|
|
8
|
+
receipt: z.string().min(1).max(10000),
|
|
9
|
+
userId: z.string().min(1).max(256),
|
|
10
|
+
productId: z.string().min(1).max(256),
|
|
11
|
+
});
|
|
12
|
+
export function createValidateRouter(config, store) {
|
|
13
|
+
const router = Router();
|
|
14
|
+
router.post(ROUTES.VALIDATE, async (req, res) => {
|
|
15
|
+
let platform;
|
|
16
|
+
let receipt;
|
|
17
|
+
let userId;
|
|
18
|
+
let productId;
|
|
19
|
+
try {
|
|
20
|
+
({ platform, receipt, userId, productId } = validateSchema.parse(req.body));
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (err instanceof z.ZodError) {
|
|
24
|
+
const response = {
|
|
25
|
+
valid: false,
|
|
26
|
+
subscription: null,
|
|
27
|
+
error: err.issues.map((e) => e.message).join(', '),
|
|
28
|
+
};
|
|
29
|
+
res.status(400).json(response);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
let sub = null;
|
|
36
|
+
if (platform === 'apple') {
|
|
37
|
+
if (!config.apple) {
|
|
38
|
+
const response = {
|
|
39
|
+
valid: false,
|
|
40
|
+
subscription: null,
|
|
41
|
+
error: 'Apple configuration not provided',
|
|
42
|
+
};
|
|
43
|
+
res.status(500).json(response);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
sub = await validateAppleReceipt(receipt, config.apple);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (!config.google) {
|
|
50
|
+
const response = {
|
|
51
|
+
valid: false,
|
|
52
|
+
subscription: null,
|
|
53
|
+
error: 'Google configuration not provided',
|
|
54
|
+
};
|
|
55
|
+
res.status(500).json(response);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
sub = await validateGoogleReceipt(receipt, productId, config.google);
|
|
59
|
+
}
|
|
60
|
+
if (!sub) {
|
|
61
|
+
const response = {
|
|
62
|
+
valid: false,
|
|
63
|
+
subscription: null,
|
|
64
|
+
error: 'Receipt validation failed',
|
|
65
|
+
};
|
|
66
|
+
res.status(422).json(response);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Attach the userId from the request
|
|
70
|
+
sub.userId = userId;
|
|
71
|
+
// Persist to the store
|
|
72
|
+
await store.save(sub);
|
|
73
|
+
const response = {
|
|
74
|
+
valid: true,
|
|
75
|
+
subscription: sub,
|
|
76
|
+
};
|
|
77
|
+
res.status(200).json(response);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.error('[onesub/validate] Unexpected error:', err);
|
|
81
|
+
const response = {
|
|
82
|
+
valid: false,
|
|
83
|
+
subscription: null,
|
|
84
|
+
error: 'Internal server error during receipt validation',
|
|
85
|
+
};
|
|
86
|
+
res.status(500).json(response);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return router;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/routes/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import type { OneSubServerConfig } from '@onesub/shared';
|
|
3
|
+
import type { SubscriptionStore } from '../store.js';
|
|
4
|
+
export declare function createWebhookRouter(config: OneSubServerConfig, store: SubscriptionStore): Router;
|
|
5
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAGV,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA0ErD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAoLR"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
3
|
+
import { ROUTES, SUBSCRIPTION_STATUS } from '@onesub/shared';
|
|
4
|
+
import { decodeAppleNotification, decodeJws } from '../providers/apple.js';
|
|
5
|
+
import { decodeGoogleNotification, validateGoogleReceipt, isGoogleActiveNotification, isGoogleCanceledNotification, isGoogleExpiredNotification, } from '../providers/google.js';
|
|
6
|
+
/**
|
|
7
|
+
* Google's public JWKS endpoint used to verify Pub/Sub push JWT tokens.
|
|
8
|
+
*/
|
|
9
|
+
const GOOGLE_JWKS_URL = 'https://www.googleapis.com/oauth2/v3/certs';
|
|
10
|
+
// Lazily initialised — the JWKS fetch only occurs when the endpoint is first hit
|
|
11
|
+
// and pushAudience is configured.
|
|
12
|
+
let googleJwks = null;
|
|
13
|
+
function getGoogleJwks() {
|
|
14
|
+
if (!googleJwks) {
|
|
15
|
+
googleJwks = createRemoteJWKSet(new URL(GOOGLE_JWKS_URL));
|
|
16
|
+
}
|
|
17
|
+
return googleJwks;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verifies the `Authorization: Bearer <token>` header as a Google-signed JWT
|
|
21
|
+
* and checks that the `aud` claim matches `expectedAudience`.
|
|
22
|
+
*
|
|
23
|
+
* Returns `true` when verification succeeds, `false` otherwise.
|
|
24
|
+
*/
|
|
25
|
+
async function verifyGooglePushToken(req, expectedAudience) {
|
|
26
|
+
const authHeader = req.headers['authorization'];
|
|
27
|
+
if (typeof authHeader !== 'string' || !authHeader.startsWith('Bearer ')) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const token = authHeader.slice('Bearer '.length).trim();
|
|
31
|
+
try {
|
|
32
|
+
await jwtVerify(token, getGoogleJwks(), { audience: expectedAudience });
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Apple V2 notification types that indicate the subscription is now active.
|
|
41
|
+
*/
|
|
42
|
+
const APPLE_ACTIVE_TYPES = new Set([
|
|
43
|
+
'SUBSCRIBED',
|
|
44
|
+
'DID_RENEW',
|
|
45
|
+
'DID_RECOVER',
|
|
46
|
+
'OFFER_REDEEMED',
|
|
47
|
+
]);
|
|
48
|
+
/**
|
|
49
|
+
* Apple V2 notification types that indicate cancellation.
|
|
50
|
+
*/
|
|
51
|
+
const APPLE_CANCELED_TYPES = new Set([
|
|
52
|
+
'REVOKE',
|
|
53
|
+
'REFUND',
|
|
54
|
+
'CONSUMPTION_REQUEST',
|
|
55
|
+
]);
|
|
56
|
+
/**
|
|
57
|
+
* Apple V2 notification types that indicate expiry.
|
|
58
|
+
*/
|
|
59
|
+
const APPLE_EXPIRED_TYPES = new Set([
|
|
60
|
+
'EXPIRED',
|
|
61
|
+
'GRACE_PERIOD_EXPIRED',
|
|
62
|
+
]);
|
|
63
|
+
export function createWebhookRouter(config, store) {
|
|
64
|
+
const router = Router();
|
|
65
|
+
/**
|
|
66
|
+
* POST /onesub/webhook/apple
|
|
67
|
+
*
|
|
68
|
+
* Receives Apple App Store Server Notifications V2.
|
|
69
|
+
* Apple sends a JWS-signed payload with a `signedPayload` field.
|
|
70
|
+
*/
|
|
71
|
+
router.post(ROUTES.WEBHOOK_APPLE, async (req, res) => {
|
|
72
|
+
// Apple sends the notification as { signedPayload: "<JWS>" }.
|
|
73
|
+
// Only the JWS-signed path is accepted. Pre-decoded payloads are rejected
|
|
74
|
+
// because they bypass signature verification and allow arbitrary state changes.
|
|
75
|
+
const body = req.body;
|
|
76
|
+
if (!body.signedPayload) {
|
|
77
|
+
res.status(400).json({ error: 'Missing signedPayload' });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let payload;
|
|
81
|
+
try {
|
|
82
|
+
payload = await decodeJws(body.signedPayload, config.apple?.skipJwsVerification);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error('[onesub/webhook/apple] Failed to decode signedPayload:', err);
|
|
86
|
+
res.status(400).json({ error: 'Invalid signedPayload' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const decoded = await decodeAppleNotification(payload, config.apple?.skipJwsVerification);
|
|
90
|
+
if (!decoded) {
|
|
91
|
+
// Could be a test notification or unsupported type — acknowledge it
|
|
92
|
+
res.status(200).json({ received: true });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const { originalTransactionId, status, willRenew, expiresAt } = decoded;
|
|
96
|
+
const notificationType = payload.notificationType;
|
|
97
|
+
// Derive final status from the notification type (overrides the JWS-derived status
|
|
98
|
+
// when there is an explicit signal like REVOKE or EXPIRED).
|
|
99
|
+
let finalStatus = status;
|
|
100
|
+
if (APPLE_CANCELED_TYPES.has(notificationType))
|
|
101
|
+
finalStatus = SUBSCRIPTION_STATUS.CANCELED;
|
|
102
|
+
else if (APPLE_EXPIRED_TYPES.has(notificationType))
|
|
103
|
+
finalStatus = SUBSCRIPTION_STATUS.EXPIRED;
|
|
104
|
+
else if (APPLE_ACTIVE_TYPES.has(notificationType))
|
|
105
|
+
finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
|
|
106
|
+
try {
|
|
107
|
+
const existing = await store.getByTransactionId(originalTransactionId);
|
|
108
|
+
if (existing) {
|
|
109
|
+
const updated = {
|
|
110
|
+
...existing,
|
|
111
|
+
status: finalStatus,
|
|
112
|
+
willRenew,
|
|
113
|
+
expiresAt,
|
|
114
|
+
};
|
|
115
|
+
await store.save(updated);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// We have no record of this transaction — log and acknowledge.
|
|
119
|
+
// This can happen for purchases made before the server started.
|
|
120
|
+
console.warn('[onesub/webhook/apple] Received notification for unknown transaction:', originalTransactionId);
|
|
121
|
+
}
|
|
122
|
+
res.status(200).json({ received: true });
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error('[onesub/webhook/apple] Store update error:', err);
|
|
126
|
+
res.status(500).json({ error: 'Failed to update subscription' });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
/**
|
|
130
|
+
* POST /onesub/webhook/google
|
|
131
|
+
*
|
|
132
|
+
* Receives Google Play Real-Time Developer Notifications (RTDN) via Pub/Sub push.
|
|
133
|
+
* The body is a standard Pub/Sub push message with base64-encoded data.
|
|
134
|
+
*/
|
|
135
|
+
router.post(ROUTES.WEBHOOK_GOOGLE, async (req, res) => {
|
|
136
|
+
// Verify Google-signed JWT when pushAudience is configured.
|
|
137
|
+
// If pushAudience is not set, authentication is skipped for backward compatibility.
|
|
138
|
+
if (config.google?.pushAudience) {
|
|
139
|
+
const authenticated = await verifyGooglePushToken(req, config.google.pushAudience);
|
|
140
|
+
if (!authenticated) {
|
|
141
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const body = req.body;
|
|
146
|
+
if (!body.message?.data) {
|
|
147
|
+
res.status(400).json({ error: 'Missing message.data' });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const notification = decodeGoogleNotification(body);
|
|
151
|
+
if (!notification) {
|
|
152
|
+
// Test notification or unknown type — acknowledge
|
|
153
|
+
res.status(200).json({ received: true });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const { notificationType, purchaseToken, subscriptionId, packageName } = notification;
|
|
157
|
+
// Validate the package name matches our config
|
|
158
|
+
if (config.google?.packageName && packageName !== config.google.packageName) {
|
|
159
|
+
console.warn('[onesub/webhook/google] Package name mismatch:', packageName, '!==', config.google.packageName);
|
|
160
|
+
res.status(400).json({ error: 'Package name mismatch' });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
// Try to find the existing record by purchase token (used as originalTransactionId)
|
|
165
|
+
const existing = await store.getByTransactionId(purchaseToken);
|
|
166
|
+
let finalStatus;
|
|
167
|
+
if (isGoogleActiveNotification(notificationType)) {
|
|
168
|
+
finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
|
|
169
|
+
}
|
|
170
|
+
else if (isGoogleCanceledNotification(notificationType)) {
|
|
171
|
+
finalStatus = SUBSCRIPTION_STATUS.CANCELED;
|
|
172
|
+
}
|
|
173
|
+
else if (isGoogleExpiredNotification(notificationType)) {
|
|
174
|
+
finalStatus = SUBSCRIPTION_STATUS.EXPIRED;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Unhandled notification type (paused, deferred, etc.) — re-fetch from Play API
|
|
178
|
+
finalStatus = SUBSCRIPTION_STATUS.ACTIVE; // optimistic; re-fetch below will correct it
|
|
179
|
+
}
|
|
180
|
+
if (existing) {
|
|
181
|
+
// Re-validate against Play API to get the latest expiry date
|
|
182
|
+
let updated = { ...existing, status: finalStatus };
|
|
183
|
+
if (config.google?.serviceAccountKey) {
|
|
184
|
+
const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
|
|
185
|
+
if (fresh) {
|
|
186
|
+
updated = {
|
|
187
|
+
...existing,
|
|
188
|
+
status: fresh.status,
|
|
189
|
+
expiresAt: fresh.expiresAt,
|
|
190
|
+
willRenew: fresh.willRenew,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
await store.save(updated);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Unknown purchase token — attempt to create a record via Play API
|
|
198
|
+
if (config.google?.serviceAccountKey) {
|
|
199
|
+
const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
|
|
200
|
+
if (fresh) {
|
|
201
|
+
// userId is unknown at webhook time — use purchaseToken as placeholder
|
|
202
|
+
fresh.userId = purchaseToken;
|
|
203
|
+
await store.save(fresh);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
console.warn('[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch:', purchaseToken);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
res.status(200).json({ received: true });
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
console.error('[onesub/webhook/google] Error handling notification:', err);
|
|
214
|
+
res.status(500).json({ error: 'Failed to process notification' });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
return router;
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SubscriptionInfo } from '@onesub/shared';
|
|
2
|
+
/**
|
|
3
|
+
* Pluggable subscription store interface.
|
|
4
|
+
* Default is in-memory. Replace with a PostgreSQL/Redis implementation
|
|
5
|
+
* by passing `store` in OneSubServerConfig.
|
|
6
|
+
*/
|
|
7
|
+
export interface SubscriptionStore {
|
|
8
|
+
save(sub: SubscriptionInfo): Promise<void>;
|
|
9
|
+
getByUserId(userId: string): Promise<SubscriptionInfo | null>;
|
|
10
|
+
getByTransactionId(txId: string): Promise<SubscriptionInfo | null>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* In-memory implementation — suitable for development and testing.
|
|
14
|
+
* Data is lost on process restart.
|
|
15
|
+
*/
|
|
16
|
+
export declare class InMemorySubscriptionStore implements SubscriptionStore {
|
|
17
|
+
private readonly byUserId;
|
|
18
|
+
private readonly byTransactionId;
|
|
19
|
+
save(sub: SubscriptionInfo): Promise<void>;
|
|
20
|
+
getByUserId(userId: string): Promise<SubscriptionInfo | null>;
|
|
21
|
+
getByTransactionId(txId: string): Promise<SubscriptionInfo | null>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAC9D,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CACpE;AAED;;;GAGG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;IACjE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuC;IAEjE,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAI7D,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;CAGzE"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory implementation — suitable for development and testing.
|
|
3
|
+
* Data is lost on process restart.
|
|
4
|
+
*/
|
|
5
|
+
export class InMemorySubscriptionStore {
|
|
6
|
+
byUserId = new Map();
|
|
7
|
+
byTransactionId = new Map();
|
|
8
|
+
async save(sub) {
|
|
9
|
+
this.byUserId.set(sub.userId, sub);
|
|
10
|
+
this.byTransactionId.set(sub.originalTransactionId, sub);
|
|
11
|
+
}
|
|
12
|
+
async getByUserId(userId) {
|
|
13
|
+
return this.byUserId.get(userId) ?? null;
|
|
14
|
+
}
|
|
15
|
+
async getByTransactionId(txId) {
|
|
16
|
+
return this.byTransactionId.get(txId) ?? null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,MAAM,OAAO,yBAAyB;IACnB,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC/C,eAAe,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEvE,KAAK,CAAC,IAAI,CAAC,GAAqB;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAc;QAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,IAAY;QACnC,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAChD,CAAC;CACF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SubscriptionInfo } from '@onesub/shared';
|
|
2
|
+
import type { SubscriptionStore } from '../store.js';
|
|
3
|
+
/**
|
|
4
|
+
* PostgreSQL-backed subscription store.
|
|
5
|
+
*
|
|
6
|
+
* Uses raw `pg` (node-postgres) to avoid Prisma / ORM dependencies.
|
|
7
|
+
* The `pg` package must be installed separately:
|
|
8
|
+
*
|
|
9
|
+
* npm install pg
|
|
10
|
+
* npm install -D @types/pg
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const store = new PostgresSubscriptionStore(process.env.DATABASE_URL!);
|
|
14
|
+
* await store.initSchema();
|
|
15
|
+
* app.use(createOneSubMiddleware({ ...config, store }));
|
|
16
|
+
*/
|
|
17
|
+
export declare class PostgresSubscriptionStore implements SubscriptionStore {
|
|
18
|
+
private readonly connectionString;
|
|
19
|
+
private poolPromise;
|
|
20
|
+
constructor(connectionString: string);
|
|
21
|
+
private getPool;
|
|
22
|
+
/**
|
|
23
|
+
* Creates the `onesub_subscriptions` table and indexes if they do not
|
|
24
|
+
* already exist. Call this once during application startup.
|
|
25
|
+
*/
|
|
26
|
+
initSchema(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Upserts the given subscription.
|
|
29
|
+
* If a row with the same `original_transaction_id` already exists it is
|
|
30
|
+
* updated in place; otherwise a new row is inserted.
|
|
31
|
+
*/
|
|
32
|
+
save(sub: SubscriptionInfo): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the most recently updated subscription for the given user, or
|
|
35
|
+
* `null` if no record exists.
|
|
36
|
+
*
|
|
37
|
+
* "Most recent" is determined by `updated_at DESC` so that the latest
|
|
38
|
+
* status change is always returned when a user has multiple subscriptions.
|
|
39
|
+
*/
|
|
40
|
+
getByUserId(userId: string): Promise<SubscriptionInfo | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Returns the subscription identified by `originalTransactionId` (the
|
|
43
|
+
* table primary key), or `null` if it does not exist.
|
|
44
|
+
*/
|
|
45
|
+
getByTransactionId(txId: string): Promise<SubscriptionInfo | null>;
|
|
46
|
+
/** Gracefully close the underlying connection pool. */
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;IAIrD,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAF7C,OAAO,CAAC,WAAW,CAA2C;gBAEjC,gBAAgB,EAAE,MAAM;IAErD,OAAO,CAAC,OAAO;IAkBf;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBjC;;;;OAIG;IACG,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BhD;;;;;;OAMG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAanE;;;OAGG;IACG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAWxE,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
|