@onesub/server 0.13.0 → 0.13.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/dist/__tests__/error-codes.test.js +8 -3
- package/dist/__tests__/error-codes.test.js.map +1 -1
- package/dist/__tests__/redis-store.test.js +58 -9
- package/dist/__tests__/redis-store.test.js.map +1 -1
- package/dist/__tests__/webhook-apple.test.js +247 -0
- package/dist/__tests__/webhook-apple.test.js.map +1 -0
- package/dist/__tests__/webhook-google.test.js +255 -0
- package/dist/__tests__/webhook-google.test.js.map +1 -0
- package/dist/cache.js +0 -4
- package/dist/cache.js.map +1 -1
- package/dist/index.cjs +10 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/apple.js +121 -1
- package/dist/providers/apple.js.map +1 -1
- package/dist/providers/google.js +42 -0
- package/dist/providers/google.js.map +1 -1
- package/dist/routes/admin.js +40 -0
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/apple-offer.js +72 -0
- package/dist/routes/apple-offer.js.map +1 -0
- package/dist/routes/purchase.d.ts.map +1 -1
- package/dist/routes/purchase.js +21 -4
- package/dist/routes/purchase.js.map +1 -1
- package/dist/routes/webhook-apple.js +145 -0
- package/dist/routes/webhook-apple.js.map +1 -0
- package/dist/routes/webhook-google.js +204 -0
- package/dist/routes/webhook-google.js.map +1 -0
- package/dist/routes/webhook.js +9 -451
- package/dist/routes/webhook.js.map +1 -1
- package/dist/stores/redis.js +65 -23
- package/dist/stores/redis.js.map +1 -1
- package/dist/webhook-events.js +9 -8
- package/dist/webhook-events.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,145 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-apple.js","sourceRoot":"","sources":["../../src/routes/webhook-apple.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,204 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-google.js","sourceRoot":"","sources":["../../src/routes/webhook-google.ts"],"names":[],"mappings":"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"}
|