@onesub/server 0.14.0 → 0.16.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__/google-subscriptions-v2.test.d.ts +1 -1
- package/dist/__tests__/receipt-age.test.d.ts +7 -0
- package/dist/__tests__/receipt-age.test.d.ts.map +1 -0
- package/dist/__tests__/tracing.test.d.ts +2 -0
- package/dist/__tests__/tracing.test.d.ts.map +1 -0
- package/dist/cache.d.ts +5 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/index.cjs +178 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +178 -55
- package/dist/index.js.map +1 -1
- package/dist/providers/apple.d.ts.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/mock.d.ts.map +1 -1
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/purchase.d.ts.map +1 -1
- package/dist/routes/validate.d.ts.map +1 -1
- package/dist/routes/webhook-apple.d.ts.map +1 -1
- package/dist/routes/webhook-google.d.ts +1 -1
- package/dist/routes/webhook-google.d.ts.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/stores/postgres.d.ts.map +1 -1
- package/dist/stores/redis.d.ts +1 -0
- package/dist/stores/redis.d.ts.map +1 -1
- package/dist/tracing.d.ts.map +1 -1
- package/dist/webhook-events.d.ts +10 -0
- package/dist/webhook-events.d.ts.map +1 -1
- package/package.json +5 -3
- 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__/cache.test.js +0 -34
- package/dist/__tests__/cache.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 -173
- 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__/openapi.test.js +0 -32
- package/dist/__tests__/openapi.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__/redis-store.test.js +0 -166
- package/dist/__tests__/redis-store.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-apple.test.js +0 -247
- package/dist/__tests__/webhook-apple.test.js.map +0 -1
- package/dist/__tests__/webhook-events.test.js +0 -36
- package/dist/__tests__/webhook-events.test.js.map +0 -1
- package/dist/__tests__/webhook-google.test.js +0 -255
- package/dist/__tests__/webhook-google.test.js.map +0 -1
- package/dist/__tests__/webhook-queue.test.js +0 -26
- package/dist/__tests__/webhook-queue.test.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/cache.js +0 -39
- package/dist/cache.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/openapi.js +0 -196
- package/dist/openapi.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 -630
- package/dist/providers/apple.js.map +0 -1
- package/dist/providers/google.js +0 -564
- 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 -327
- package/dist/routes/admin.js.map +0 -1
- package/dist/routes/apple-offer.js +0 -72
- package/dist/routes/apple-offer.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 -204
- 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-apple.js +0 -145
- package/dist/routes/webhook-apple.js.map +0 -1
- package/dist/routes/webhook-google.js +0 -204
- package/dist/routes/webhook-google.js.map +0 -1
- package/dist/routes/webhook.js +0 -19
- 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/redis.js +0 -297
- package/dist/stores/redis.js.map +0 -1
- package/dist/stores/schema.js +0 -67
- package/dist/stores/schema.js.map +0 -1
- package/dist/tracing.js +0 -74
- package/dist/tracing.js.map +0 -1
- package/dist/webhook-events.js +0 -58
- package/dist/webhook-events.js.map +0 -1
- package/dist/webhook-queue.js +0 -108
- package/dist/webhook-queue.js.map +0 -1
package/dist/routes/validate.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { ROUTES, ONESUB_ERROR_CODE } from '@onesub/shared';
|
|
4
|
-
import { validateAppleReceipt } from '../providers/apple.js';
|
|
5
|
-
import { validateGoogleReceipt, acknowledgeGoogleSubscription } from '../providers/google.js';
|
|
6
|
-
import { log } from '../logger.js';
|
|
7
|
-
import { sendError, sendZodError } from '../errors.js';
|
|
8
|
-
const NO_SUB = { valid: false, subscription: null };
|
|
9
|
-
const validateSchema = z.object({
|
|
10
|
-
platform: z.enum(['apple', 'google']),
|
|
11
|
-
receipt: z.string().min(1).max(10000),
|
|
12
|
-
userId: z.string().min(1).max(256),
|
|
13
|
-
productId: z.string().min(1).max(256),
|
|
14
|
-
});
|
|
15
|
-
export function createValidateRouter(config, store) {
|
|
16
|
-
const router = Router();
|
|
17
|
-
router.post(ROUTES.VALIDATE, async (req, res) => {
|
|
18
|
-
let platform;
|
|
19
|
-
let receipt;
|
|
20
|
-
let userId;
|
|
21
|
-
let productId;
|
|
22
|
-
try {
|
|
23
|
-
({ platform, receipt, userId, productId } = validateSchema.parse(req.body));
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
if (err instanceof z.ZodError) {
|
|
27
|
-
sendZodError(res, err, NO_SUB);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
let sub = null;
|
|
34
|
-
if (platform === 'apple') {
|
|
35
|
-
if (!config.apple) {
|
|
36
|
-
sendError(res, 500, ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, 'Apple configuration not provided', NO_SUB);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
sub = await validateAppleReceipt(receipt, config.apple);
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
if (!config.google) {
|
|
43
|
-
sendError(res, 500, ONESUB_ERROR_CODE.GOOGLE_CONFIG_MISSING, 'Google configuration not provided', NO_SUB);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
sub = await validateGoogleReceipt(receipt, productId, config.google);
|
|
47
|
-
}
|
|
48
|
-
if (!sub) {
|
|
49
|
-
sendError(res, 422, ONESUB_ERROR_CODE.RECEIPT_VALIDATION_FAILED, 'Receipt validation failed', NO_SUB);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
sub.userId = userId;
|
|
53
|
-
await store.save(sub);
|
|
54
|
-
// Google requires acknowledgement within 3 days of purchase or the
|
|
55
|
-
// transaction is auto-refunded. Fire-and-forget — entitlement is already
|
|
56
|
-
// saved, ack is idempotent on the Play side.
|
|
57
|
-
if (platform === 'google' && config.google) {
|
|
58
|
-
void acknowledgeGoogleSubscription(receipt, productId, config.google);
|
|
59
|
-
}
|
|
60
|
-
const response = { valid: true, subscription: sub };
|
|
61
|
-
res.status(200).json(response);
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
log.error('[onesub/validate] Unexpected error:', err);
|
|
65
|
-
sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, 'Internal server error during receipt validation', NO_SUB);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
return router;
|
|
69
|
-
}
|
|
70
|
-
//# sourceMappingURL=validate.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/routes/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAW,CAAC;AAE7D,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;IACrC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAClC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACtC,CAAC,CAAC;AAEH,MAAM,UAAU,oBAAoB,CAClC,MAA0B,EAC1B,KAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QACjE,IAAI,QAAgB,CAAC;QACrB,IAAI,OAAe,CAAC;QACpB,IAAI,MAAc,CAAC;QACnB,IAAI,SAAiB,CAAC;QAEtB,IAAI,CAAC;YACH,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC9B,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACH,IAAI,GAAG,GAAG,IAAI,CAAC;YAEf,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBAClB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,oBAAoB,EAAE,kCAAkC,EAAE,MAAM,CAAC,CAAC;oBACxG,OAAO;gBACT,CAAC;gBACD,GAAG,GAAG,MAAM,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YAC1D,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;oBACnB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,qBAAqB,EAAE,mCAAmC,EAAE,MAAM,CAAC,CAAC;oBAC1G,OAAO;gBACT,CAAC;gBACD,GAAG,GAAG,MAAM,qBAAqB,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACvE,CAAC;YAED,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,CAAC,CAAC;gBACtG,OAAO;YACT,CAAC;YAED,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;YACpB,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAEtB,mEAAmE;YACnE,yEAAyE;YACzE,6CAA6C;YAC7C,IAAI,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC3C,KAAK,6BAA6B,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACxE,CAAC;YAED,MAAM,QAAQ,GAA4B,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;YAC7E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC;YACtD,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,cAAc,EAAE,iDAAiD,EAAE,MAAM,CAAC,CAAC;QACnH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { SUBSCRIPTION_STATUS, ONESUB_ERROR_CODE } from '@onesub/shared';
|
|
2
|
-
import { decodeAppleNotification, decodeJws, sendAppleConsumptionResponse, fetchAppleSubscriptionStatus, } from '../providers/apple.js';
|
|
3
|
-
import { log } from '../logger.js';
|
|
4
|
-
import { sendError } from '../errors.js';
|
|
5
|
-
const APPLE_ACTIVE_TYPES = new Set(['SUBSCRIBED', 'DID_RENEW', 'DID_RECOVER', 'OFFER_REDEEMED']);
|
|
6
|
-
const APPLE_CANCELED_TYPES = new Set(['REVOKE', 'REFUND', 'CONSUMPTION_REQUEST']);
|
|
7
|
-
const APPLE_EXPIRED_TYPES = new Set(['EXPIRED']);
|
|
8
|
-
function mapAppleNotificationStatus(notificationType, subtype) {
|
|
9
|
-
if (notificationType === 'DID_FAIL_TO_RENEW') {
|
|
10
|
-
return subtype === 'GRACE_PERIOD'
|
|
11
|
-
? SUBSCRIPTION_STATUS.GRACE_PERIOD
|
|
12
|
-
: SUBSCRIPTION_STATUS.ON_HOLD;
|
|
13
|
-
}
|
|
14
|
-
if (notificationType === 'GRACE_PERIOD_EXPIRED')
|
|
15
|
-
return SUBSCRIPTION_STATUS.ON_HOLD;
|
|
16
|
-
if (APPLE_CANCELED_TYPES.has(notificationType))
|
|
17
|
-
return SUBSCRIPTION_STATUS.CANCELED;
|
|
18
|
-
if (APPLE_EXPIRED_TYPES.has(notificationType))
|
|
19
|
-
return SUBSCRIPTION_STATUS.EXPIRED;
|
|
20
|
-
if (APPLE_ACTIVE_TYPES.has(notificationType))
|
|
21
|
-
return SUBSCRIPTION_STATUS.ACTIVE;
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
export async function handleAppleWebhook(req, res, config, store, purchaseStore, webhookEventStore) {
|
|
25
|
-
const body = req.body;
|
|
26
|
-
if (!body.signedPayload) {
|
|
27
|
-
sendError(res, 400, ONESUB_ERROR_CODE.MISSING_SIGNED_PAYLOAD, 'Missing signedPayload');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
let payload;
|
|
31
|
-
try {
|
|
32
|
-
payload = await decodeJws(body.signedPayload, config.apple?.skipJwsVerification);
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
log.error('[onesub/webhook/apple] Failed to decode signedPayload:', err);
|
|
36
|
-
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_SIGNED_PAYLOAD, 'Invalid signedPayload');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (webhookEventStore && typeof payload.notificationUUID === 'string') {
|
|
40
|
-
const fresh = await webhookEventStore.markIfNew('apple', payload.notificationUUID);
|
|
41
|
-
if (!fresh) {
|
|
42
|
-
log.info('[onesub/webhook/apple] dedupe: already processed', payload.notificationUUID);
|
|
43
|
-
res.status(200).json({ received: true, deduped: true });
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const decoded = await decodeAppleNotification(payload, config.apple?.skipJwsVerification);
|
|
48
|
-
if (!decoded) {
|
|
49
|
-
res.status(200).json({ received: true });
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const { originalTransactionId, transactionId, type, productId, bundleId, environment, status, willRenew, expiresAt, appAccountToken, inAppOwnershipType, } = decoded;
|
|
53
|
-
const notificationType = payload.notificationType;
|
|
54
|
-
const subtype = payload.subtype;
|
|
55
|
-
const mapped = mapAppleNotificationStatus(notificationType, subtype);
|
|
56
|
-
const finalStatus = mapped ?? status;
|
|
57
|
-
if (notificationType === 'CONSUMPTION_REQUEST' &&
|
|
58
|
-
config.apple?.consumptionInfoProvider &&
|
|
59
|
-
transactionId &&
|
|
60
|
-
productId) {
|
|
61
|
-
const provider = config.apple.consumptionInfoProvider;
|
|
62
|
-
const appleConfig = config.apple;
|
|
63
|
-
void (async () => {
|
|
64
|
-
try {
|
|
65
|
-
const info = await provider({
|
|
66
|
-
transactionId,
|
|
67
|
-
originalTransactionId,
|
|
68
|
-
productId,
|
|
69
|
-
bundleId: bundleId ?? appleConfig.bundleId,
|
|
70
|
-
environment,
|
|
71
|
-
});
|
|
72
|
-
if (info) {
|
|
73
|
-
await sendAppleConsumptionResponse(transactionId, info, appleConfig, {
|
|
74
|
-
sandbox: environment === 'Sandbox',
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
log.warn('[onesub/webhook/apple] consumptionInfoProvider failed:', err);
|
|
80
|
-
}
|
|
81
|
-
})();
|
|
82
|
-
}
|
|
83
|
-
const isOneTimePurchase = type === 'Consumable' || type === 'Non-Consumable';
|
|
84
|
-
const isRefundOrRevoke = APPLE_CANCELED_TYPES.has(notificationType);
|
|
85
|
-
try {
|
|
86
|
-
if (isOneTimePurchase && isRefundOrRevoke) {
|
|
87
|
-
const lookupId = transactionId ?? originalTransactionId;
|
|
88
|
-
const removed = await purchaseStore.deletePurchaseByTransactionId(lookupId);
|
|
89
|
-
if (!removed) {
|
|
90
|
-
log.warn('[onesub/webhook/apple] IAP refund for unknown transaction:', lookupId);
|
|
91
|
-
}
|
|
92
|
-
res.status(200).json({ received: true });
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const existing = await store.getByTransactionId(originalTransactionId);
|
|
96
|
-
if (existing) {
|
|
97
|
-
const isSubscriptionRefund = isRefundOrRevoke && !isOneTimePurchase && notificationType !== 'CONSUMPTION_REQUEST';
|
|
98
|
-
const keepEntitlement = isSubscriptionRefund && config.refundPolicy === 'until_expiry';
|
|
99
|
-
// If the stored userId was a fallback (originalTransactionId) and we now
|
|
100
|
-
// have a real appAccountToken, correct it. Never overwrite a userId that
|
|
101
|
-
// the client set explicitly (which would differ from originalTransactionId).
|
|
102
|
-
const correctedUserId = appAccountToken && existing.userId === originalTransactionId
|
|
103
|
-
? appAccountToken
|
|
104
|
-
: existing.userId;
|
|
105
|
-
if (correctedUserId !== existing.userId) {
|
|
106
|
-
log.info('[onesub/webhook/apple] correcting userId from originalTransactionId to appAccountToken:', correctedUserId);
|
|
107
|
-
}
|
|
108
|
-
const updated = keepEntitlement
|
|
109
|
-
? { ...existing, userId: correctedUserId, willRenew: false }
|
|
110
|
-
: {
|
|
111
|
-
...existing,
|
|
112
|
-
userId: correctedUserId,
|
|
113
|
-
status: finalStatus,
|
|
114
|
-
willRenew,
|
|
115
|
-
expiresAt: expiresAt ?? existing.expiresAt,
|
|
116
|
-
};
|
|
117
|
-
await store.save(updated);
|
|
118
|
-
}
|
|
119
|
-
else if (config.apple?.issuerId && config.apple?.keyId && config.apple?.privateKey) {
|
|
120
|
-
const fresh = await fetchAppleSubscriptionStatus(originalTransactionId, config.apple, {
|
|
121
|
-
sandbox: environment === 'Sandbox',
|
|
122
|
-
});
|
|
123
|
-
if (fresh) {
|
|
124
|
-
fresh.userId = appAccountToken ?? originalTransactionId;
|
|
125
|
-
if (inAppOwnershipType === 'FAMILY_SHARED') {
|
|
126
|
-
const source = appAccountToken ? 'appAccountToken' : 'originalTransactionId (fallback)';
|
|
127
|
-
log.info(`[onesub/webhook/apple] FAMILY_SHARED — userId: ${fresh.userId} (source: ${source})`);
|
|
128
|
-
}
|
|
129
|
-
await store.save(fresh);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
log.warn('[onesub/webhook/apple] Unknown transaction and Status API returned no record:', originalTransactionId);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
log.warn('[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover):', originalTransactionId);
|
|
137
|
-
}
|
|
138
|
-
res.status(200).json({ received: true });
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
log.error('[onesub/webhook/apple] Store update error:', err);
|
|
142
|
-
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, 'Failed to update subscription');
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
//# sourceMappingURL=webhook-apple.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-apple.js","sourceRoot":"","sources":["../../src/routes/webhook-apple.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
2
|
-
import { SUBSCRIPTION_STATUS, ONESUB_ERROR_CODE } from '@onesub/shared';
|
|
3
|
-
import { decodeGoogleNotification, decodeGoogleVoidedNotification, decodeGoogleOneTimeProductNotification, validateGoogleReceipt, acknowledgeGoogleProduct, isGoogleActiveNotification, isGoogleCanceledNotification, isGoogleExpiredNotification, isGoogleGracePeriodNotification, isGoogleOnHoldNotification, isGooglePausedNotification, isGooglePriceChangeConfirmedNotification, } from '../providers/google.js';
|
|
4
|
-
import { log } from '../logger.js';
|
|
5
|
-
import { sendError } from '../errors.js';
|
|
6
|
-
const GOOGLE_JWKS_URL = 'https://www.googleapis.com/oauth2/v3/certs';
|
|
7
|
-
let googleJwks = null;
|
|
8
|
-
function getGoogleJwks() {
|
|
9
|
-
if (!googleJwks)
|
|
10
|
-
googleJwks = createRemoteJWKSet(new URL(GOOGLE_JWKS_URL));
|
|
11
|
-
return googleJwks;
|
|
12
|
-
}
|
|
13
|
-
export async function verifyGooglePushToken(req, expectedAudience) {
|
|
14
|
-
const authHeader = req.headers['authorization'];
|
|
15
|
-
if (typeof authHeader !== 'string' || !authHeader.startsWith('Bearer '))
|
|
16
|
-
return false;
|
|
17
|
-
try {
|
|
18
|
-
await jwtVerify(authHeader.slice('Bearer '.length).trim(), getGoogleJwks(), {
|
|
19
|
-
audience: expectedAudience,
|
|
20
|
-
});
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export async function handleGoogleWebhook(req, res, config, store, purchaseStore, webhookEventStore) {
|
|
28
|
-
if (config.google?.pushAudience) {
|
|
29
|
-
const authenticated = await verifyGooglePushToken(req, config.google.pushAudience);
|
|
30
|
-
if (!authenticated) {
|
|
31
|
-
sendError(res, 401, ONESUB_ERROR_CODE.UNAUTHORIZED, 'Unauthorized');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const body = req.body;
|
|
36
|
-
if (!body.message?.data) {
|
|
37
|
-
sendError(res, 400, ONESUB_ERROR_CODE.MISSING_MESSAGE_DATA, 'Missing message.data');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (webhookEventStore && typeof body.message.messageId === 'string') {
|
|
41
|
-
const fresh = await webhookEventStore.markIfNew('google', body.message.messageId);
|
|
42
|
-
if (!fresh) {
|
|
43
|
-
log.info('[onesub/webhook/google] dedupe: already processed', body.message.messageId);
|
|
44
|
-
res.status(200).json({ received: true, deduped: true });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const voided = decodeGoogleVoidedNotification(body);
|
|
49
|
-
if (voided) {
|
|
50
|
-
if (config.google?.packageName && voided.packageName !== config.google.packageName) {
|
|
51
|
-
log.warn('[onesub/webhook/google] voided package name mismatch:', voided.packageName, '!==', config.google.packageName);
|
|
52
|
-
sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, 'Package name mismatch');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
if (voided.productType === 1) {
|
|
57
|
-
const existing = await store.getByTransactionId(voided.purchaseToken);
|
|
58
|
-
if (existing) {
|
|
59
|
-
const updated = config.refundPolicy === 'until_expiry'
|
|
60
|
-
? { ...existing, willRenew: false }
|
|
61
|
-
: { ...existing, status: SUBSCRIPTION_STATUS.CANCELED };
|
|
62
|
-
await store.save(updated);
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
log.warn('[onesub/webhook/google] voided subscription for unknown purchaseToken:', voided.purchaseToken);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
const removed = await purchaseStore.deletePurchaseByTransactionId(voided.orderId);
|
|
70
|
-
if (!removed) {
|
|
71
|
-
log.warn('[onesub/webhook/google] voided IAP for unknown orderId:', voided.orderId);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
res.status(200).json({ received: true });
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
log.error('[onesub/webhook/google] voided notification error:', err);
|
|
78
|
-
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, 'Failed to process voided notification');
|
|
79
|
-
}
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
// oneTimeProductNotification — consumable / non-consumable purchase signal.
|
|
83
|
-
// The notification carries no userId, so we cannot create a purchase record
|
|
84
|
-
// here (the client's POST /onesub/purchase/validate is the authoritative path).
|
|
85
|
-
// For PURCHASED we acknowledge to prevent the 3-day auto-refund window.
|
|
86
|
-
const oneTimeProduct = decodeGoogleOneTimeProductNotification(body);
|
|
87
|
-
if (oneTimeProduct) {
|
|
88
|
-
if (config.google?.packageName && oneTimeProduct.packageName !== config.google.packageName) {
|
|
89
|
-
log.warn('[onesub/webhook/google] oneTimeProduct package name mismatch:', oneTimeProduct.packageName, '!==', config.google.packageName);
|
|
90
|
-
sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, 'Package name mismatch');
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
const { notificationType, purchaseToken, sku } = oneTimeProduct;
|
|
95
|
-
if (notificationType === 1 /* PURCHASED */) {
|
|
96
|
-
log.info('[onesub/webhook/google] oneTimeProduct PURCHASED:', sku);
|
|
97
|
-
if (config.google?.serviceAccountKey && config.google.packageName) {
|
|
98
|
-
void acknowledgeGoogleProduct(purchaseToken, sku, config.google).catch((err) => log.warn(`[onesub/webhook/google] oneTimeProduct ack failed for SKU ${sku} — 3-day auto-refund risk:`, err));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
// notificationType === 2: CANCELED (user aborted before purchase completed)
|
|
103
|
-
log.info('[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion):', sku);
|
|
104
|
-
}
|
|
105
|
-
res.status(200).json({ received: true });
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
log.error('[onesub/webhook/google] oneTimeProduct error:', err);
|
|
109
|
-
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, 'Failed to process oneTimeProduct notification');
|
|
110
|
-
}
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const notification = decodeGoogleNotification(body);
|
|
114
|
-
if (!notification) {
|
|
115
|
-
res.status(200).json({ received: true });
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const { notificationType, purchaseToken, subscriptionId, packageName } = notification;
|
|
119
|
-
if (config.google?.packageName && packageName !== config.google.packageName) {
|
|
120
|
-
log.warn('[onesub/webhook/google] Package name mismatch:', packageName, '!==', config.google.packageName);
|
|
121
|
-
sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, 'Package name mismatch');
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
const existing = await store.getByTransactionId(purchaseToken);
|
|
126
|
-
let finalStatus;
|
|
127
|
-
if (isGoogleActiveNotification(notificationType)) {
|
|
128
|
-
finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
|
|
129
|
-
}
|
|
130
|
-
else if (isGoogleGracePeriodNotification(notificationType)) {
|
|
131
|
-
finalStatus = SUBSCRIPTION_STATUS.GRACE_PERIOD;
|
|
132
|
-
}
|
|
133
|
-
else if (isGoogleOnHoldNotification(notificationType)) {
|
|
134
|
-
finalStatus = SUBSCRIPTION_STATUS.ON_HOLD;
|
|
135
|
-
}
|
|
136
|
-
else if (isGooglePausedNotification(notificationType)) {
|
|
137
|
-
finalStatus = SUBSCRIPTION_STATUS.PAUSED;
|
|
138
|
-
}
|
|
139
|
-
else if (isGooglePriceChangeConfirmedNotification(notificationType)) {
|
|
140
|
-
finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
|
|
141
|
-
}
|
|
142
|
-
else if (isGoogleCanceledNotification(notificationType)) {
|
|
143
|
-
finalStatus = SUBSCRIPTION_STATUS.CANCELED;
|
|
144
|
-
}
|
|
145
|
-
else if (isGoogleExpiredNotification(notificationType)) {
|
|
146
|
-
finalStatus = SUBSCRIPTION_STATUS.EXPIRED;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
|
|
150
|
-
}
|
|
151
|
-
if (isGooglePriceChangeConfirmedNotification(notificationType) && config.google?.onPriceChangeConfirmed) {
|
|
152
|
-
const hook = config.google.onPriceChangeConfirmed;
|
|
153
|
-
void Promise.resolve()
|
|
154
|
-
.then(() => hook({ purchaseToken, subscriptionId, packageName }))
|
|
155
|
-
.catch((err) => log.warn('[onesub/webhook/google] onPriceChangeConfirmed hook failed:', err));
|
|
156
|
-
}
|
|
157
|
-
if (existing) {
|
|
158
|
-
let updated = { ...existing, status: finalStatus };
|
|
159
|
-
if (config.google?.serviceAccountKey) {
|
|
160
|
-
const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
|
|
161
|
-
if (fresh) {
|
|
162
|
-
const preserveNotificationStatus = finalStatus === SUBSCRIPTION_STATUS.GRACE_PERIOD ||
|
|
163
|
-
finalStatus === SUBSCRIPTION_STATUS.ON_HOLD;
|
|
164
|
-
updated = {
|
|
165
|
-
...existing,
|
|
166
|
-
status: preserveNotificationStatus ? finalStatus : fresh.status,
|
|
167
|
-
expiresAt: fresh.expiresAt,
|
|
168
|
-
willRenew: fresh.willRenew,
|
|
169
|
-
autoResumeTime: fresh.autoResumeTime,
|
|
170
|
-
linkedPurchaseToken: fresh.linkedPurchaseToken ?? existing.linkedPurchaseToken,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
await store.save(updated);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
if (config.google?.serviceAccountKey) {
|
|
178
|
-
const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
|
|
179
|
-
if (fresh) {
|
|
180
|
-
if (fresh.linkedPurchaseToken) {
|
|
181
|
-
const previous = await store.getByTransactionId(fresh.linkedPurchaseToken);
|
|
182
|
-
fresh.userId = previous ? previous.userId : purchaseToken;
|
|
183
|
-
if (previous) {
|
|
184
|
-
log.info(`[onesub/webhook/google] inherited userId ${previous.userId} from linkedPurchaseToken ${fresh.linkedPurchaseToken} → new token ${purchaseToken}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
fresh.userId = purchaseToken;
|
|
189
|
-
}
|
|
190
|
-
await store.save(fresh);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
log.warn('[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch:', purchaseToken);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
res.status(200).json({ received: true });
|
|
198
|
-
}
|
|
199
|
-
catch (err) {
|
|
200
|
-
log.error('[onesub/webhook/google] Error handling notification:', err);
|
|
201
|
-
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, 'Failed to process notification');
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
//# sourceMappingURL=webhook-google.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-google.js","sourceRoot":"","sources":["../../src/routes/webhook-google.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAGrD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExE,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,sCAAsC,EACtC,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC1B,4BAA4B,EAC5B,2BAA2B,EAC3B,+BAA+B,EAC/B,0BAA0B,EAC1B,0BAA0B,EAC1B,wCAAwC,GACzC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,MAAM,eAAe,GAAG,4CAA4C,CAAC;AAErE,IAAI,UAAU,GAAiD,IAAI,CAAC;AAEpE,SAAS,aAAa;IACpB,IAAI,CAAC,UAAU;QAAE,UAAU,GAAG,kBAAkB,CAAC,IAAI,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC;IAC3E,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAY,EAAE,gBAAwB;IAChF,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAChD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACtF,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,EAAE;YAC1E,QAAQ,EAAE,gBAAgB;SAC3B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAY,EACZ,GAAa,EACb,MAA0B,EAC1B,KAAwB,EACxB,aAA4B,EAC5B,iBAAqC;IAErC,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;QAChC,MAAM,aAAa,GAAG,MAAM,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACnF,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;YACpE,OAAO;QACT,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAA0C,CAAC;IAE5D,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;QACxB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,oBAAoB,EAAE,sBAAsB,CAAC,CAAC;QACpF,OAAO;IACT,CAAC;IAED,IAAI,iBAAiB,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QACpE,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAClF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CAAC,mDAAmD,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACtF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,OAAO;QACT,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,8BAA8B,CAAC,IAAiC,CAAC,CAAC;IACjF,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,IAAI,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACnF,GAAG,CAAC,IAAI,CAAC,uDAAuD,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YACxH,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,CAAC;YACtF,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,IAAI,MAAM,CAAC,WAAW,KAAK,CAAC,EAAE,CAAC;gBAC7B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;gBACtE,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,KAAK,cAAc;wBACpD,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE;wBACnC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,mBAAmB,CAAC,QAAQ,EAAE,CAAC;oBAC1D,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC5B,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,IAAI,CAAC,wEAAwE,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;gBAC3G,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,6BAA6B,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAClF,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,GAAG,CAAC,IAAI,CAAC,yDAAyD,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;gBACtF,CAAC;YACH,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,oDAAoD,EAAE,GAAG,CAAC,CAAC;YACrE,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,yBAAyB,EAAE,uCAAuC,CAAC,CAAC;QAC5G,CAAC;QACD,OAAO;IACT,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,gFAAgF;IAChF,wEAAwE;IACxE,MAAM,cAAc,GAAG,sCAAsC,CAAC,IAAiC,CAAC,CAAC;IACjG,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,IAAI,cAAc,CAAC,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAC3F,GAAG,CAAC,IAAI,CAAC,+DAA+D,EAAE,cAAc,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YACxI,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,CAAC;YACtF,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC;YAChE,IAAI,gBAAgB,KAAK,CAAC,CAAC,eAAe,EAAE,CAAC;gBAC3C,GAAG,CAAC,IAAI,CAAC,mDAAmD,EAAE,GAAG,CAAC,CAAC;gBACnE,IAAI,MAAM,CAAC,MAAM,EAAE,iBAAiB,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;oBAClE,KAAK,wBAAwB,CAAC,aAAa,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CACpE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,6DAA6D,GAAG,4BAA4B,EAAE,GAAG,CAAC,CACrH,CAAC;gBACJ,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,4EAA4E;gBAC5E,GAAG,CAAC,IAAI,CAAC,mEAAmE,EAAE,GAAG,CAAC,CAAC;YACrF,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,+CAA+C,EAAE,GAAG,CAAC,CAAC;YAChE,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,yBAAyB,EAAE,+CAA+C,CAAC,CAAC;QACpH,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,wBAAwB,CAAC,IAAiC,CAAC,CAAC;IACjF,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,OAAO;IACT,CAAC;IAED,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,YAAY,CAAC;IAEtF,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,IAAI,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC5E,GAAG,CAAC,IAAI,CAAC,gDAAgD,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC1G,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,CAAC;QACtF,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;QAE/D,IAAI,WAAuC,CAAC;QAC5C,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACjD,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC;QAC3C,CAAC;aAAM,IAAI,+BAA+B,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC7D,WAAW,GAAG,mBAAmB,CAAC,YAAY,CAAC;QACjD,CAAC;aAAM,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACxD,WAAW,GAAG,mBAAmB,CAAC,OAAO,CAAC;QAC5C,CAAC;aAAM,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACxD,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC;QAC3C,CAAC;aAAM,IAAI,wCAAwC,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACtE,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC;QAC3C,CAAC;aAAM,IAAI,4BAA4B,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC1D,WAAW,GAAG,mBAAmB,CAAC,QAAQ,CAAC;QAC7C,CAAC;aAAM,IAAI,2BAA2B,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACzD,WAAW,GAAG,mBAAmB,CAAC,OAAO,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC;QAC3C,CAAC;QAED,IAAI,wCAAwC,CAAC,gBAAgB,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,CAAC;YACxG,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC;YAClD,KAAK,OAAO,CAAC,OAAO,EAAE;iBACnB,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;iBAChE,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,6DAA6D,EAAE,GAAG,CAAC,CAAC,CAAC;QAClG,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,OAAO,GAAqB,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;YAErE,IAAI,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;gBACrC,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC,aAAa,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;gBACxF,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,0BAA0B,GAC9B,WAAW,KAAK,mBAAmB,CAAC,YAAY;wBAChD,WAAW,KAAK,mBAAmB,CAAC,OAAO,CAAC;oBAC9C,OAAO,GAAG;wBACR,GAAG,QAAQ;wBACX,MAAM,EAAE,0BAA0B,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM;wBAC/D,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,cAAc,EAAE,KAAK,CAAC,cAAc;wBACpC,mBAAmB,EAAE,KAAK,CAAC,mBAAmB,IAAI,QAAQ,CAAC,mBAAmB;qBAC/E,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,IAAI,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;gBACrC,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC,aAAa,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;gBACxF,IAAI,KAAK,EAAE,CAAC;oBACV,IAAI,KAAK,CAAC,mBAAmB,EAAE,CAAC;wBAC9B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;wBAC3E,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC;wBAC1D,IAAI,QAAQ,EAAE,CAAC;4BACb,GAAG,CAAC,IAAI,CAAC,4CAA4C,QAAQ,CAAC,MAAM,6BAA6B,KAAK,CAAC,mBAAmB,gBAAgB,aAAa,EAAE,CAAC,CAAC;wBAC7J,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC;oBAC/B,CAAC;oBACD,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,IAAI,CAAC,sFAAsF,EAAE,aAAa,CAAC,CAAC;YAClH,CAAC;QACH,CAAC;QAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,sDAAsD,EAAE,GAAG,CAAC,CAAC;QACvE,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,yBAAyB,EAAE,gCAAgC,CAAC,CAAC;IACrG,CAAC;AACH,CAAC"}
|
package/dist/routes/webhook.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { ROUTES } from '@onesub/shared';
|
|
3
|
-
import { handleAppleWebhook } from './webhook-apple.js';
|
|
4
|
-
import { handleGoogleWebhook } from './webhook-google.js';
|
|
5
|
-
/**
|
|
6
|
-
* Retry / durability semantics.
|
|
7
|
-
*
|
|
8
|
-
* Both Apple and Google retry on any non-2xx response.
|
|
9
|
-
* 4xx — payload is unusable; do NOT retry.
|
|
10
|
-
* 5xx — transient failure; source will retry.
|
|
11
|
-
* 2xx — processed or intentionally ignored.
|
|
12
|
-
*/
|
|
13
|
-
export function createWebhookRouter(config, store, purchaseStore, webhookEventStore) {
|
|
14
|
-
const router = Router();
|
|
15
|
-
router.post(ROUTES.WEBHOOK_APPLE, (req, res) => handleAppleWebhook(req, res, config, store, purchaseStore, webhookEventStore));
|
|
16
|
-
router.post(ROUTES.WEBHOOK_GOOGLE, (req, res) => handleGoogleWebhook(req, res, config, store, purchaseStore, webhookEventStore));
|
|
17
|
-
return router;
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=webhook.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAA0B,EAC1B,KAAwB,EACxB,aAA4B,EAC5B,iBAAqC;IAErC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE,CAChE,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAC9E,CAAC;IAEF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE,CACjE,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAC/E,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
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":"AAiEA;;;GAGG;AACH,MAAM,OAAO,yBAAyB;IACpC,0EAA0E;IAC1E,qEAAqE;IACrE,qEAAqE;IACpD,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;IACjD,eAAe,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEvE,KAAK,CAAC,IAAI,CAAC,GAAqB;QAC9B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;QAEzD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACrD,qEAAqE;QACrE,8EAA8E;QAC9E,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,qBAAqB,KAAK,GAAG,CAAC,qBAAqB,CAAC,CAAC;QAC/F,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAc;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,MAAc;QACjC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,IAAY;QACnC,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,IAAyB;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QAChC,wEAAwE;QACxE,0EAA0E;QAC1E,8DAA8D;QAC9D,MAAM,GAAG,GAAuB,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,KAAK,MAAM,CAAC,IAAI,IAAI;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;YAC1D,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;YAC1D,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC;YACnE,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO;YACL,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;YAC7C,KAAK,EAAE,QAAQ,CAAC,MAAM;YACtB,KAAK;YACL,MAAM;SACP,CAAC;IACJ,CAAC;CACF;AAsCD;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IACf,eAAe,GAAG,IAAI,GAAG,EAAwB,CAAC;IAClD,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IAE9D,KAAK,CAAC,YAAY,CAAC,QAAsB;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAClE,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACxC,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,mCAAmC,CAA8B,CAAC;gBACxF,GAAG,CAAC,IAAI,GAAG,mCAAmC,CAAC;gBAC/C,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,OAAO,CAAC,yBAAyB;QACnC,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACtD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,MAAc;QACvC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,0BAA0B,CAAC,IAAY;QAC3C,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,SAAiB;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QAC7B,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,aAAqB,EAAE,SAAiB;QAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACzD,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5B,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC;QAClC,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,OAAO,GAAG,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACnD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACjD,+BAA+B;QAC/B,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,aAAa,CAAC,CAAC;QACtG,IAAI,OAAO,CAAC,MAAM;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;;YACrD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACrC,0BAA0B;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,MAAc,EAAE,SAAiB;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS;gBAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;QAC9E,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,6BAA6B,CAAC,aAAqB;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACzD,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAC5D,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,aAAa,CACzC,CAAC;QACF,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;;YACrD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|