@onesub/server 0.1.0 → 0.3.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__/providers.test.d.ts +6 -0
- package/dist/__tests__/providers.test.d.ts.map +1 -0
- package/dist/__tests__/providers.test.js +261 -0
- package/dist/__tests__/providers.test.js.map +1 -0
- package/dist/__tests__/store.test.js +53 -1
- package/dist/__tests__/store.test.js.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/apple.d.ts +20 -0
- package/dist/providers/apple.d.ts.map +1 -1
- package/dist/providers/apple.js +77 -2
- package/dist/providers/apple.js.map +1 -1
- package/dist/providers/google.d.ts +19 -0
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +73 -0
- package/dist/providers/google.js.map +1 -1
- package/dist/routes/purchase.d.ts +5 -0
- package/dist/routes/purchase.d.ts.map +1 -0
- package/dist/routes/purchase.js +184 -0
- package/dist/routes/purchase.js.map +1 -0
- package/dist/store.d.ts +23 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +26 -0
- package/dist/store.js.map +1 -1
- package/dist/stores/postgres.d.ts +32 -2
- package/dist/stores/postgres.d.ts.map +1 -1
- package/dist/stores/postgres.js +119 -0
- package/dist/stores/postgres.js.map +1 -1
- package/package.json +5 -3
|
@@ -28,6 +28,25 @@ type GoogleNotificationType = (typeof GOOGLE_NOTIFICATION_TYPE)[keyof typeof GOO
|
|
|
28
28
|
* @param config - Google config with packageName and optional serviceAccountKey
|
|
29
29
|
*/
|
|
30
30
|
export declare function validateGoogleReceipt(receipt: string, productId: string, config: GoogleConfig): Promise<SubscriptionInfo | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Result of validating a Google Play product (consumable / non-consumable) receipt.
|
|
33
|
+
*/
|
|
34
|
+
export interface GoogleProductResult {
|
|
35
|
+
/** orderId — unique per Google Play transaction, safe deduplication key */
|
|
36
|
+
transactionId: string;
|
|
37
|
+
purchasedAt: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validate a Google Play purchase token for a one-time product (consumable or
|
|
41
|
+
* non-consumable). Uses purchases.products, not purchases.subscriptions.
|
|
42
|
+
*
|
|
43
|
+
* Security checks applied beyond basic API verification:
|
|
44
|
+
* - purchaseState must be 0 (completed)
|
|
45
|
+
* - consumptionState checked for consumables: already-consumed tokens indicate replay
|
|
46
|
+
* - Receipt age limited to 72 hours
|
|
47
|
+
* - orderId used as transactionId (per-purchase unique, unlike purchaseToken)
|
|
48
|
+
*/
|
|
49
|
+
export declare function validateGoogleProductReceipt(purchaseToken: string, productId: string, config: GoogleConfig, type?: 'consumable' | 'non_consumable'): Promise<GoogleProductResult | null>;
|
|
31
50
|
/**
|
|
32
51
|
* Decode a Google RTDN Pub/Sub notification.
|
|
33
52
|
* Returns the extracted subscription notification data, or null for test pings.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGtG,KAAK,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAuB9D;;;GAGG;AACH,QAAA,MAAM,wBAAwB;;;;;;;;;;;;;;CAcpB,CAAC;AAEX,KAAK,sBAAsB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGtG,KAAK,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAuB9D;;;GAGG;AACH,QAAA,MAAM,wBAAwB;;;;;;;;;;;;;;CAcpB,CAAC;AAEX,KAAK,sBAAsB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAmMvG;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA6BlC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE,YAAY,GAAG,gBAAmC,GACvD,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAgDrC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG;IAC5E,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,IAAI,CAuBP;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAQ5F;AAED,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAK9F;AAED,wBAAgB,2BAA2B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAE7F"}
|
package/dist/providers/google.js
CHANGED
|
@@ -18,6 +18,8 @@ const GOOGLE_NOTIFICATION_TYPE = {
|
|
|
18
18
|
SUBSCRIPTION_REVOKED: 12,
|
|
19
19
|
SUBSCRIPTION_EXPIRED: 13,
|
|
20
20
|
};
|
|
21
|
+
/** Maximum age for product receipts (72 hours). */
|
|
22
|
+
const MAX_PRODUCT_RECEIPT_AGE_MS = 72 * 60 * 60 * 1000;
|
|
21
23
|
/**
|
|
22
24
|
* Module-level token cache. Keyed by the raw serviceAccountKey string so that
|
|
23
25
|
* different service accounts (rare but possible) are cached independently.
|
|
@@ -109,6 +111,23 @@ async function fetchSubscriptionPurchase(packageName, productId, purchaseToken,
|
|
|
109
111
|
}
|
|
110
112
|
return resp.json();
|
|
111
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Fetch a one-time product purchase from the Google Play Developer API.
|
|
116
|
+
* Uses purchases.products instead of purchases.subscriptions.
|
|
117
|
+
*/
|
|
118
|
+
async function fetchProductPurchase(packageName, productId, purchaseToken, accessToken) {
|
|
119
|
+
const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/` +
|
|
120
|
+
`${encodeURIComponent(packageName)}/purchases/products/` +
|
|
121
|
+
`${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}`;
|
|
122
|
+
const resp = await fetch(url, {
|
|
123
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
124
|
+
});
|
|
125
|
+
if (!resp.ok) {
|
|
126
|
+
const body = await resp.text();
|
|
127
|
+
throw new Error(`[onesub/google] Play Products API error ${resp.status}: ${body}`);
|
|
128
|
+
}
|
|
129
|
+
return resp.json();
|
|
130
|
+
}
|
|
112
131
|
/**
|
|
113
132
|
* Derive a SubscriptionStatus from a Google Play purchase resource.
|
|
114
133
|
*/
|
|
@@ -158,6 +177,60 @@ export async function validateGoogleReceipt(receipt, productId, config) {
|
|
|
158
177
|
willRenew: purchase.autoRenewing ?? false,
|
|
159
178
|
};
|
|
160
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Validate a Google Play purchase token for a one-time product (consumable or
|
|
182
|
+
* non-consumable). Uses purchases.products, not purchases.subscriptions.
|
|
183
|
+
*
|
|
184
|
+
* Security checks applied beyond basic API verification:
|
|
185
|
+
* - purchaseState must be 0 (completed)
|
|
186
|
+
* - consumptionState checked for consumables: already-consumed tokens indicate replay
|
|
187
|
+
* - Receipt age limited to 72 hours
|
|
188
|
+
* - orderId used as transactionId (per-purchase unique, unlike purchaseToken)
|
|
189
|
+
*/
|
|
190
|
+
export async function validateGoogleProductReceipt(purchaseToken, productId, config, type = 'non_consumable') {
|
|
191
|
+
if (!config.serviceAccountKey) {
|
|
192
|
+
console.warn('[onesub/google] No serviceAccountKey — cannot validate product receipt');
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
let purchase;
|
|
196
|
+
try {
|
|
197
|
+
const token = await getCachedAccessToken(config.serviceAccountKey);
|
|
198
|
+
purchase = await fetchProductPurchase(config.packageName, productId, purchaseToken, token);
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
console.error('[onesub/google] Product receipt validation failed:', err);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
// purchaseState 0 = completed (1 = canceled, 2 = pending)
|
|
205
|
+
if (purchase.purchaseState !== 0) {
|
|
206
|
+
console.warn('[onesub/google] Purchase not completed, state:', purchase.purchaseState);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
// For consumables: consumptionState 1 means already consumed by a previous request.
|
|
210
|
+
// This is the primary replay-attack signal for consumables on Android.
|
|
211
|
+
if (type === 'consumable' && purchase.consumptionState === 1) {
|
|
212
|
+
console.warn('[onesub/google] Consumable already consumed — possible replay attack');
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
// Reject receipts older than 72 hours
|
|
216
|
+
if (purchase.purchaseTimeMillis) {
|
|
217
|
+
const purchaseTime = parseInt(purchase.purchaseTimeMillis, 10);
|
|
218
|
+
if (Date.now() - purchaseTime > MAX_PRODUCT_RECEIPT_AGE_MS) {
|
|
219
|
+
console.warn('[onesub/google] Product receipt too old (>72h)');
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!purchase.orderId) {
|
|
224
|
+
console.warn('[onesub/google] No orderId in product purchase');
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
transactionId: purchase.orderId,
|
|
229
|
+
purchasedAt: purchase.purchaseTimeMillis
|
|
230
|
+
? new Date(parseInt(purchase.purchaseTimeMillis, 10)).toISOString()
|
|
231
|
+
: new Date().toISOString(),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
161
234
|
/**
|
|
162
235
|
* Decode a Google RTDN Pub/Sub notification.
|
|
163
236
|
* Returns the extracted subscription notification data, or null for test pings.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAyBrD;;;GAGG;AACH,MAAM,wBAAwB,GAAG;IAC/B,sBAAsB,EAAE,CAAC;IACzB,oBAAoB,EAAE,CAAC;IACvB,qBAAqB,EAAE,CAAC;IACxB,sBAAsB,EAAE,CAAC;IACzB,oBAAoB,EAAE,CAAC;IACvB,4BAA4B,EAAE,CAAC;IAC/B,sBAAsB,EAAE,CAAC;IACzB,mCAAmC,EAAE,CAAC;IACtC,qBAAqB,EAAE,CAAC;IACxB,mBAAmB,EAAE,EAAE;IACvB,mCAAmC,EAAE,EAAE;IACvC,oBAAoB,EAAE,EAAE;IACxB,oBAAoB,EAAE,EAAE;CAChB,CAAC;
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/providers/google.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 { PurchaseStore } from '../store.js';
|
|
4
|
+
export declare function createPurchaseRouter(config: OneSubServerConfig, purchaseStore: PurchaseStore): Router;
|
|
5
|
+
//# sourceMappingURL=purchase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purchase.d.ts","sourceRoot":"","sources":["../../src/routes/purchase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAIV,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiBjD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,aAAa,GAC3B,MAAM,CAwLR"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ROUTES, PURCHASE_TYPE } from '@onesub/shared';
|
|
4
|
+
import { validateAppleConsumableReceipt } from '../providers/apple.js';
|
|
5
|
+
import { validateGoogleProductReceipt } from '../providers/google.js';
|
|
6
|
+
const validatePurchaseSchema = 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
|
+
type: z.enum([PURCHASE_TYPE.CONSUMABLE, PURCHASE_TYPE.NON_CONSUMABLE]),
|
|
12
|
+
});
|
|
13
|
+
const purchaseStatusQuerySchema = z.object({
|
|
14
|
+
userId: z.string().min(1).max(256),
|
|
15
|
+
productId: z.string().min(1).max(256).optional(),
|
|
16
|
+
});
|
|
17
|
+
export function createPurchaseRouter(config, purchaseStore) {
|
|
18
|
+
const router = Router();
|
|
19
|
+
/**
|
|
20
|
+
* POST /onesub/purchase/validate
|
|
21
|
+
*
|
|
22
|
+
* Validate a receipt for a consumable or non-consumable purchase.
|
|
23
|
+
* - Non-consumables: rejected if the user already owns the product.
|
|
24
|
+
* - Consumables: always recorded (multiple purchases allowed).
|
|
25
|
+
*
|
|
26
|
+
* Uses platform-specific product validators (purchases.products API) instead
|
|
27
|
+
* of the subscription validator. Key differences:
|
|
28
|
+
* - Apple: validates tx.type === 'Consumable'|'NonConsumable', uses transactionId
|
|
29
|
+
* (not originalTransactionId) as the dedup key, enforces 72h receipt age
|
|
30
|
+
* - Google: checks consumptionState for consumables (replay prevention),
|
|
31
|
+
* enforces 72h receipt age, uses orderId as the dedup key
|
|
32
|
+
*/
|
|
33
|
+
router.post(ROUTES.VALIDATE_PURCHASE, async (req, res) => {
|
|
34
|
+
let body;
|
|
35
|
+
try {
|
|
36
|
+
body = validatePurchaseSchema.parse(req.body);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err instanceof z.ZodError) {
|
|
40
|
+
const response = {
|
|
41
|
+
valid: false,
|
|
42
|
+
purchase: null,
|
|
43
|
+
error: err.issues.map((e) => e.message).join(', '),
|
|
44
|
+
};
|
|
45
|
+
res.status(400).json(response);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
const { platform, receipt, userId, productId, type } = body;
|
|
51
|
+
try {
|
|
52
|
+
// Non-consumable duplicate check (before receipt validation to fail fast)
|
|
53
|
+
if (type === PURCHASE_TYPE.NON_CONSUMABLE) {
|
|
54
|
+
const alreadyOwned = await purchaseStore.hasPurchased(userId, productId);
|
|
55
|
+
if (alreadyOwned) {
|
|
56
|
+
const response = {
|
|
57
|
+
valid: false,
|
|
58
|
+
purchase: null,
|
|
59
|
+
error: 'NON_CONSUMABLE_ALREADY_OWNED',
|
|
60
|
+
};
|
|
61
|
+
res.status(409).json(response);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Validate receipt via the appropriate platform-specific product validator.
|
|
66
|
+
// Note: these are separate from the subscription validators — they call
|
|
67
|
+
// purchases.products (not purchases.subscriptions) and apply product-specific
|
|
68
|
+
// security checks (type validation, consumptionState, receipt age).
|
|
69
|
+
let transactionId = null;
|
|
70
|
+
let purchasedAt = new Date().toISOString();
|
|
71
|
+
if (platform === 'apple') {
|
|
72
|
+
if (!config.apple) {
|
|
73
|
+
const response = {
|
|
74
|
+
valid: false,
|
|
75
|
+
purchase: null,
|
|
76
|
+
error: 'Apple configuration not provided',
|
|
77
|
+
};
|
|
78
|
+
res.status(500).json(response);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const result = await validateAppleConsumableReceipt(receipt, config.apple, productId);
|
|
82
|
+
if (result) {
|
|
83
|
+
transactionId = result.transactionId;
|
|
84
|
+
purchasedAt = result.purchasedAt;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (!config.google) {
|
|
89
|
+
const response = {
|
|
90
|
+
valid: false,
|
|
91
|
+
purchase: null,
|
|
92
|
+
error: 'Google configuration not provided',
|
|
93
|
+
};
|
|
94
|
+
res.status(500).json(response);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const result = await validateGoogleProductReceipt(receipt, productId, config.google, type === PURCHASE_TYPE.CONSUMABLE ? 'consumable' : 'non_consumable');
|
|
98
|
+
if (result) {
|
|
99
|
+
transactionId = result.transactionId;
|
|
100
|
+
purchasedAt = result.purchasedAt;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!transactionId) {
|
|
104
|
+
const response = {
|
|
105
|
+
valid: false,
|
|
106
|
+
purchase: null,
|
|
107
|
+
error: 'Receipt validation failed',
|
|
108
|
+
};
|
|
109
|
+
res.status(422).json(response);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Idempotency check — same transaction submitted twice returns the existing record
|
|
113
|
+
const existing = await purchaseStore.getPurchaseByTransactionId(transactionId);
|
|
114
|
+
if (existing) {
|
|
115
|
+
const response = {
|
|
116
|
+
valid: true,
|
|
117
|
+
purchase: existing,
|
|
118
|
+
};
|
|
119
|
+
res.status(200).json(response);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const purchase = {
|
|
123
|
+
userId,
|
|
124
|
+
productId,
|
|
125
|
+
platform,
|
|
126
|
+
type,
|
|
127
|
+
transactionId,
|
|
128
|
+
purchasedAt,
|
|
129
|
+
quantity: 1,
|
|
130
|
+
};
|
|
131
|
+
await purchaseStore.savePurchase(purchase);
|
|
132
|
+
const response = {
|
|
133
|
+
valid: true,
|
|
134
|
+
purchase,
|
|
135
|
+
};
|
|
136
|
+
res.status(200).json(response);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error('[onesub/purchase/validate] Unexpected error:', err);
|
|
140
|
+
const response = {
|
|
141
|
+
valid: false,
|
|
142
|
+
purchase: null,
|
|
143
|
+
error: 'Internal server error during purchase validation',
|
|
144
|
+
};
|
|
145
|
+
res.status(500).json(response);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
/**
|
|
149
|
+
* GET /onesub/purchase/status?userId=xxx[&productId=yyy]
|
|
150
|
+
*
|
|
151
|
+
* Returns all purchases for a user, optionally filtered by productId.
|
|
152
|
+
*/
|
|
153
|
+
router.get(ROUTES.PURCHASE_STATUS, async (req, res) => {
|
|
154
|
+
let query;
|
|
155
|
+
try {
|
|
156
|
+
query = purchaseStatusQuerySchema.parse(req.query);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err instanceof z.ZodError) {
|
|
160
|
+
res.status(400).json({
|
|
161
|
+
purchases: [],
|
|
162
|
+
error: err.issues.map((e) => e.message).join(', '),
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
const { userId, productId } = query;
|
|
169
|
+
try {
|
|
170
|
+
let purchases = await purchaseStore.getPurchasesByUserId(userId);
|
|
171
|
+
if (productId !== undefined) {
|
|
172
|
+
purchases = purchases.filter((p) => p.productId === productId);
|
|
173
|
+
}
|
|
174
|
+
const response = { purchases };
|
|
175
|
+
res.status(200).json(response);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.error('[onesub/purchase/status] Store error:', err);
|
|
179
|
+
res.status(500).json({ purchases: [], error: 'Internal server error' });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return router;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=purchase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purchase.js","sourceRoot":"","sources":["../../src/routes/purchase.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SubscriptionInfo } from '@onesub/shared';
|
|
1
|
+
import type { SubscriptionInfo, PurchaseInfo } from '@onesub/shared';
|
|
2
2
|
/**
|
|
3
3
|
* Pluggable subscription store interface.
|
|
4
4
|
* Default is in-memory. Replace with a PostgreSQL/Redis implementation
|
|
@@ -20,4 +20,26 @@ export declare class InMemorySubscriptionStore implements SubscriptionStore {
|
|
|
20
20
|
getByUserId(userId: string): Promise<SubscriptionInfo | null>;
|
|
21
21
|
getByTransactionId(txId: string): Promise<SubscriptionInfo | null>;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Pluggable purchase store interface for consumables and non-consumables.
|
|
25
|
+
*/
|
|
26
|
+
export interface PurchaseStore {
|
|
27
|
+
savePurchase(purchase: PurchaseInfo): Promise<void>;
|
|
28
|
+
getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
|
|
29
|
+
getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
|
|
30
|
+
/** For non-consumables: check if a user has already purchased a product. */
|
|
31
|
+
hasPurchased(userId: string, productId: string): Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* In-memory implementation of PurchaseStore — suitable for development and testing.
|
|
35
|
+
* Data is lost on process restart.
|
|
36
|
+
*/
|
|
37
|
+
export declare class InMemoryPurchaseStore implements PurchaseStore {
|
|
38
|
+
private readonly byTransactionId;
|
|
39
|
+
private readonly byUserId;
|
|
40
|
+
savePurchase(purchase: PurchaseInfo): Promise<void>;
|
|
41
|
+
getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
|
|
42
|
+
getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
|
|
43
|
+
hasPurchased(userId: string, productId: string): Promise<boolean>;
|
|
44
|
+
}
|
|
23
45
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAErE;;;;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;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC9D,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACvE,4EAA4E;IAC5E,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAExD,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAI7D,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAItE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKxE"}
|
package/dist/store.js
CHANGED
|
@@ -16,4 +16,30 @@ export class InMemorySubscriptionStore {
|
|
|
16
16
|
return this.byTransactionId.get(txId) ?? null;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* In-memory implementation of PurchaseStore — suitable for development and testing.
|
|
21
|
+
* Data is lost on process restart.
|
|
22
|
+
*/
|
|
23
|
+
export class InMemoryPurchaseStore {
|
|
24
|
+
byTransactionId = new Map();
|
|
25
|
+
byUserId = new Map();
|
|
26
|
+
async savePurchase(purchase) {
|
|
27
|
+
this.byTransactionId.set(purchase.transactionId, purchase);
|
|
28
|
+
const list = this.byUserId.get(purchase.userId) ?? [];
|
|
29
|
+
list.push(purchase);
|
|
30
|
+
this.byUserId.set(purchase.userId, list);
|
|
31
|
+
}
|
|
32
|
+
async getPurchasesByUserId(userId) {
|
|
33
|
+
return this.byUserId.get(userId) ?? [];
|
|
34
|
+
}
|
|
35
|
+
async getPurchaseByTransactionId(txId) {
|
|
36
|
+
return this.byTransactionId.get(txId) ?? null;
|
|
37
|
+
}
|
|
38
|
+
async hasPurchased(userId, productId) {
|
|
39
|
+
const purchases = this.byUserId.get(userId);
|
|
40
|
+
if (!purchases)
|
|
41
|
+
return false;
|
|
42
|
+
return purchases.some((p) => p.productId === productId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
19
45
|
//# sourceMappingURL=store.js.map
|
package/dist/store.js.map
CHANGED
|
@@ -1 +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"}
|
|
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;AAaD;;;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,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;CACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SubscriptionInfo } from '@onesub/shared';
|
|
2
|
-
import type { SubscriptionStore } from '../store.js';
|
|
1
|
+
import type { SubscriptionInfo, PurchaseInfo } from '@onesub/shared';
|
|
2
|
+
import type { SubscriptionStore, PurchaseStore } from '../store.js';
|
|
3
3
|
/**
|
|
4
4
|
* PostgreSQL-backed subscription store.
|
|
5
5
|
*
|
|
@@ -46,4 +46,34 @@ export declare class PostgresSubscriptionStore implements SubscriptionStore {
|
|
|
46
46
|
/** Gracefully close the underlying connection pool. */
|
|
47
47
|
close(): Promise<void>;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* PostgreSQL-backed purchase store for consumables and non-consumables.
|
|
51
|
+
*
|
|
52
|
+
* Uses raw `pg` (node-postgres). The `pg` package must be installed separately:
|
|
53
|
+
*
|
|
54
|
+
* npm install pg
|
|
55
|
+
* npm install -D @types/pg
|
|
56
|
+
*
|
|
57
|
+
* Usage:
|
|
58
|
+
* const store = new PostgresPurchaseStore(process.env.DATABASE_URL!);
|
|
59
|
+
* await store.initSchema();
|
|
60
|
+
* app.use(createOneSubMiddleware({ ...config, purchaseStore: store }));
|
|
61
|
+
*/
|
|
62
|
+
export declare class PostgresPurchaseStore implements PurchaseStore {
|
|
63
|
+
private readonly connectionString;
|
|
64
|
+
private poolPromise;
|
|
65
|
+
constructor(connectionString: string);
|
|
66
|
+
private getPool;
|
|
67
|
+
/**
|
|
68
|
+
* Creates the `onesub_purchases` table and indexes if they do not already exist.
|
|
69
|
+
* Call this once during application startup.
|
|
70
|
+
*/
|
|
71
|
+
initSchema(): Promise<void>;
|
|
72
|
+
savePurchase(purchase: PurchaseInfo): Promise<void>;
|
|
73
|
+
getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
|
|
74
|
+
getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
|
|
75
|
+
hasPurchased(userId: string, productId: string): Promise<boolean>;
|
|
76
|
+
/** Gracefully close the underlying connection pool. */
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
}
|
|
49
79
|
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;;;;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;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IAG7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAF7C,OAAO,CAAC,WAAW,CAA2C;gBAEjC,gBAAgB,EAAE,MAAM;IAErD,OAAO,CAAC,OAAO;IAgBf;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBnD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAY7D,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAWtE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYvE,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
|
package/dist/stores/postgres.js
CHANGED
|
@@ -123,6 +123,114 @@ export class PostgresSubscriptionStore {
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* PostgreSQL-backed purchase store for consumables and non-consumables.
|
|
128
|
+
*
|
|
129
|
+
* Uses raw `pg` (node-postgres). The `pg` package must be installed separately:
|
|
130
|
+
*
|
|
131
|
+
* npm install pg
|
|
132
|
+
* npm install -D @types/pg
|
|
133
|
+
*
|
|
134
|
+
* Usage:
|
|
135
|
+
* const store = new PostgresPurchaseStore(process.env.DATABASE_URL!);
|
|
136
|
+
* await store.initSchema();
|
|
137
|
+
* app.use(createOneSubMiddleware({ ...config, purchaseStore: store }));
|
|
138
|
+
*/
|
|
139
|
+
export class PostgresPurchaseStore {
|
|
140
|
+
connectionString;
|
|
141
|
+
poolPromise = null;
|
|
142
|
+
constructor(connectionString) {
|
|
143
|
+
this.connectionString = connectionString;
|
|
144
|
+
}
|
|
145
|
+
getPool() {
|
|
146
|
+
if (!this.poolPromise) {
|
|
147
|
+
this.poolPromise = (async () => {
|
|
148
|
+
const pg = await import('pg').catch(() => {
|
|
149
|
+
throw new Error('[onesub] PostgresPurchaseStore requires the `pg` package. ' +
|
|
150
|
+
'Run: npm install pg');
|
|
151
|
+
});
|
|
152
|
+
const Pool = pg.default?.Pool ?? pg.Pool;
|
|
153
|
+
return new Pool({ connectionString: this.connectionString, max: 10 });
|
|
154
|
+
})();
|
|
155
|
+
}
|
|
156
|
+
return this.poolPromise;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Creates the `onesub_purchases` table and indexes if they do not already exist.
|
|
160
|
+
* Call this once during application startup.
|
|
161
|
+
*/
|
|
162
|
+
async initSchema() {
|
|
163
|
+
const pool = await this.getPool();
|
|
164
|
+
await pool.query(`
|
|
165
|
+
CREATE TABLE IF NOT EXISTS onesub_purchases (
|
|
166
|
+
transaction_id TEXT PRIMARY KEY,
|
|
167
|
+
user_id TEXT NOT NULL,
|
|
168
|
+
product_id TEXT NOT NULL,
|
|
169
|
+
platform TEXT NOT NULL,
|
|
170
|
+
type TEXT NOT NULL,
|
|
171
|
+
quantity INTEGER NOT NULL DEFAULT 1,
|
|
172
|
+
purchased_at TIMESTAMPTZ NOT NULL,
|
|
173
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
CREATE INDEX IF NOT EXISTS idx_onesub_purchases_user_id
|
|
177
|
+
ON onesub_purchases (user_id, purchased_at DESC);
|
|
178
|
+
|
|
179
|
+
CREATE INDEX IF NOT EXISTS idx_onesub_purchases_user_product
|
|
180
|
+
ON onesub_purchases (user_id, product_id);
|
|
181
|
+
|
|
182
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_onesub_purchases_non_consumable
|
|
183
|
+
ON onesub_purchases (user_id, product_id)
|
|
184
|
+
WHERE type = 'non_consumable';
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
async savePurchase(purchase) {
|
|
188
|
+
const pool = await this.getPool();
|
|
189
|
+
await pool.query(`INSERT INTO onesub_purchases
|
|
190
|
+
(transaction_id, user_id, product_id, platform, type, quantity, purchased_at)
|
|
191
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
192
|
+
ON CONFLICT (transaction_id) DO NOTHING`, [
|
|
193
|
+
purchase.transactionId,
|
|
194
|
+
purchase.userId,
|
|
195
|
+
purchase.productId,
|
|
196
|
+
purchase.platform,
|
|
197
|
+
purchase.type,
|
|
198
|
+
purchase.quantity,
|
|
199
|
+
purchase.purchasedAt,
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
async getPurchasesByUserId(userId) {
|
|
203
|
+
const pool = await this.getPool();
|
|
204
|
+
const result = await pool.query(`SELECT *
|
|
205
|
+
FROM onesub_purchases
|
|
206
|
+
WHERE user_id = $1
|
|
207
|
+
ORDER BY purchased_at DESC`, [userId]);
|
|
208
|
+
return result.rows.map(rowToPurchaseInfo);
|
|
209
|
+
}
|
|
210
|
+
async getPurchaseByTransactionId(txId) {
|
|
211
|
+
const pool = await this.getPool();
|
|
212
|
+
const result = await pool.query(`SELECT *
|
|
213
|
+
FROM onesub_purchases
|
|
214
|
+
WHERE transaction_id = $1`, [txId]);
|
|
215
|
+
return result.rows.length > 0 ? rowToPurchaseInfo(result.rows[0]) : null;
|
|
216
|
+
}
|
|
217
|
+
async hasPurchased(userId, productId) {
|
|
218
|
+
const pool = await this.getPool();
|
|
219
|
+
const result = await pool.query(`SELECT EXISTS(
|
|
220
|
+
SELECT 1 FROM onesub_purchases
|
|
221
|
+
WHERE user_id = $1 AND product_id = $2
|
|
222
|
+
) AS exists`, [userId, productId]);
|
|
223
|
+
return result.rows[0]?.exists === true;
|
|
224
|
+
}
|
|
225
|
+
/** Gracefully close the underlying connection pool. */
|
|
226
|
+
async close() {
|
|
227
|
+
if (this.poolPromise) {
|
|
228
|
+
const pool = await this.poolPromise;
|
|
229
|
+
await pool.end();
|
|
230
|
+
this.poolPromise = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
126
234
|
function rowToSubscriptionInfo(row) {
|
|
127
235
|
return {
|
|
128
236
|
originalTransactionId: row.original_transaction_id,
|
|
@@ -135,4 +243,15 @@ function rowToSubscriptionInfo(row) {
|
|
|
135
243
|
willRenew: row.will_renew,
|
|
136
244
|
};
|
|
137
245
|
}
|
|
246
|
+
function rowToPurchaseInfo(row) {
|
|
247
|
+
return {
|
|
248
|
+
transactionId: row.transaction_id,
|
|
249
|
+
userId: row.user_id,
|
|
250
|
+
productId: row.product_id,
|
|
251
|
+
platform: row.platform,
|
|
252
|
+
type: row.type,
|
|
253
|
+
quantity: row.quantity,
|
|
254
|
+
purchasedAt: row.purchased_at.toISOString(),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
138
257
|
//# sourceMappingURL=postgres.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,yBAAyB;IAIP;IAH7B,kFAAkF;IAC1E,WAAW,GAAsC,IAAI,CAAC;IAE9D,YAA6B,gBAAwB;QAAxB,qBAAgB,GAAhB,gBAAgB,CAAQ;IAAG,CAAC;IAEjD,OAAO;QACb,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,+DAA+D;gBAC/D,gEAAgE;gBAChE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;oBACvC,MAAM,IAAI,KAAK,CACb,gEAAgE;wBAC9D,qBAAqB,CACxB,CAAC;gBACJ,CAAC,CAAC,CAAC;gBACH,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,IAAK,EAAoD,CAAC,IAAI,CAAC;gBAC5F,OAAO,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;YACxE,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;KAehB,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,GAAqB;QAC9B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,IAAI,CAAC,KAAK,CACd;;;;;;;;;;;;4BAYsB,EACtB;YACE,GAAG,CAAC,qBAAqB;YACzB,GAAG,CAAC,MAAM;YACV,GAAG,CAAC,SAAS;YACb,GAAG,CAAC,QAAQ;YACZ,GAAG,CAAC,MAAM;YACV,GAAG,CAAC,SAAS;YACb,GAAG,CAAC,WAAW;YACf,GAAG,CAAC,SAAS;SACd,CACF,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,WAAW,CAAC,MAAc;QAC9B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;;gBAIU,EACV,CAAC,MAAM,CAAC,CACT,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/E,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,kBAAkB,CAAC,IAAY;QACnC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;2CAEqC,EACrC,CAAC,IAAI,CAAC,CACP,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/E,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC;YACpC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAC1B,CAAC;IACH,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,qBAAqB;IAGH;IAFrB,WAAW,GAAsC,IAAI,CAAC;IAE9D,YAA6B,gBAAwB;QAAxB,qBAAgB,GAAhB,gBAAgB,CAAQ;IAAG,CAAC;IAEjD,OAAO;QACb,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;oBACvC,MAAM,IAAI,KAAK,CACb,4DAA4D;wBAC1D,qBAAqB,CACxB,CAAC;gBACJ,CAAC,CAAC,CAAC;gBACH,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,IAAK,EAAoD,CAAC,IAAI,CAAC;gBAC5F,OAAO,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;YACxE,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;KAqBhB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAsB;QACvC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,IAAI,CAAC,KAAK,CACd;;;+CAGyC,EACzC;YACE,QAAQ,CAAC,aAAa;YACtB,QAAQ,CAAC,MAAM;YACf,QAAQ,CAAC,SAAS;YAClB,QAAQ,CAAC,QAAQ;YACjB,QAAQ,CAAC,IAAI;YACb,QAAQ,CAAC,QAAQ;YACjB,QAAQ,CAAC,WAAW;SACrB,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,MAAc;QACvC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;mCAG6B,EAC7B,CAAC,MAAM,CAAC,CACT,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,0BAA0B,CAAC,IAAY;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;kCAE4B,EAC5B,CAAC,IAAI,CAAC,CACP,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,SAAiB;QAClD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;mBAGa,EACb,CAAC,MAAM,EAAE,SAAS,CAAC,CACpB,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC;YACpC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAC1B,CAAC;IACH,CAAC;CACF;AAiBD,SAAS,qBAAqB,CAAC,GAAU;IACvC,OAAO;QACL,qBAAqB,EAAE,GAAG,CAAC,uBAAuB;QAClD,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,QAAQ,EAAE,GAAG,CAAC,QAAwC;QACtD,MAAM,EAAE,GAAG,CAAC,MAAoC;QAChD,SAAS,EAAE,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE;QACvC,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE;QAC3C,SAAS,EAAE,GAAG,CAAC,UAAU;KAC1B,CAAC;AACJ,CAAC;AAYD,SAAS,iBAAiB,CAAC,GAAkB;IAC3C,OAAO;QACL,aAAa,EAAE,GAAG,CAAC,cAAc;QACjC,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,QAAQ,EAAE,GAAG,CAAC,QAAoC;QAClD,IAAI,EAAE,GAAG,CAAC,IAA4B;QACtC,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE;KAC5C,CAAC;AACJ,CAAC"}
|