@onesub/server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/store.test.d.ts +2 -0
- package/dist/__tests__/store.test.d.ts.map +1 -0
- package/dist/__tests__/store.test.js +71 -0
- package/dist/__tests__/store.test.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/apple.d.ts +29 -0
- package/dist/providers/apple.d.ts.map +1 -0
- package/dist/providers/apple.js +111 -0
- package/dist/providers/apple.js.map +1 -0
- package/dist/providers/google.d.ts +48 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +203 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/routes/status.d.ts +4 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +41 -0
- package/dist/routes/status.js.map +1 -0
- package/dist/routes/validate.d.ts +5 -0
- package/dist/routes/validate.d.ts.map +1 -0
- package/dist/routes/validate.js +91 -0
- package/dist/routes/validate.js.map +1 -0
- package/dist/routes/webhook.d.ts +5 -0
- package/dist/routes/webhook.d.ts.map +1 -0
- package/dist/routes/webhook.js +219 -0
- package/dist/routes/webhook.js.map +1 -0
- package/dist/store.d.ts +23 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +19 -0
- package/dist/store.js.map +1 -0
- package/dist/stores/postgres.d.ts +49 -0
- package/dist/stores/postgres.d.ts.map +1 -0
- package/dist/stores/postgres.js +138 -0
- package/dist/stores/postgres.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL-backed subscription store.
|
|
3
|
+
*
|
|
4
|
+
* Uses raw `pg` (node-postgres) to avoid Prisma / ORM dependencies.
|
|
5
|
+
* The `pg` package must be installed separately:
|
|
6
|
+
*
|
|
7
|
+
* npm install pg
|
|
8
|
+
* npm install -D @types/pg
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const store = new PostgresSubscriptionStore(process.env.DATABASE_URL!);
|
|
12
|
+
* await store.initSchema();
|
|
13
|
+
* app.use(createOneSubMiddleware({ ...config, store }));
|
|
14
|
+
*/
|
|
15
|
+
export class PostgresSubscriptionStore {
|
|
16
|
+
connectionString;
|
|
17
|
+
// Lazy-loaded to avoid requiring `pg` unless this class is actually instantiated.
|
|
18
|
+
poolPromise = null;
|
|
19
|
+
constructor(connectionString) {
|
|
20
|
+
this.connectionString = connectionString;
|
|
21
|
+
}
|
|
22
|
+
getPool() {
|
|
23
|
+
if (!this.poolPromise) {
|
|
24
|
+
this.poolPromise = (async () => {
|
|
25
|
+
// Dynamic import so that `pg` is an optional peer dependency —
|
|
26
|
+
// consumers who only use InMemorySubscriptionStore pay no cost.
|
|
27
|
+
const pg = await import('pg').catch(() => {
|
|
28
|
+
throw new Error('[onesub] PostgresSubscriptionStore requires the `pg` package. ' +
|
|
29
|
+
'Run: npm install pg');
|
|
30
|
+
});
|
|
31
|
+
const Pool = pg.default?.Pool ?? pg.Pool;
|
|
32
|
+
return new Pool({ connectionString: this.connectionString, max: 10 });
|
|
33
|
+
})();
|
|
34
|
+
}
|
|
35
|
+
return this.poolPromise;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates the `onesub_subscriptions` table and indexes if they do not
|
|
39
|
+
* already exist. Call this once during application startup.
|
|
40
|
+
*/
|
|
41
|
+
async initSchema() {
|
|
42
|
+
const pool = await this.getPool();
|
|
43
|
+
await pool.query(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS onesub_subscriptions (
|
|
45
|
+
original_transaction_id TEXT PRIMARY KEY,
|
|
46
|
+
user_id TEXT NOT NULL,
|
|
47
|
+
product_id TEXT NOT NULL,
|
|
48
|
+
platform TEXT NOT NULL,
|
|
49
|
+
status TEXT NOT NULL,
|
|
50
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
51
|
+
purchased_at TIMESTAMPTZ NOT NULL,
|
|
52
|
+
will_renew BOOLEAN NOT NULL,
|
|
53
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_user_id
|
|
57
|
+
ON onesub_subscriptions (user_id, updated_at DESC);
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Upserts the given subscription.
|
|
62
|
+
* If a row with the same `original_transaction_id` already exists it is
|
|
63
|
+
* updated in place; otherwise a new row is inserted.
|
|
64
|
+
*/
|
|
65
|
+
async save(sub) {
|
|
66
|
+
const pool = await this.getPool();
|
|
67
|
+
await pool.query(`INSERT INTO onesub_subscriptions
|
|
68
|
+
(original_transaction_id, user_id, product_id, platform, status,
|
|
69
|
+
expires_at, purchased_at, will_renew, updated_at)
|
|
70
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
|
|
71
|
+
ON CONFLICT (original_transaction_id) DO UPDATE SET
|
|
72
|
+
user_id = EXCLUDED.user_id,
|
|
73
|
+
product_id = EXCLUDED.product_id,
|
|
74
|
+
platform = EXCLUDED.platform,
|
|
75
|
+
status = EXCLUDED.status,
|
|
76
|
+
expires_at = EXCLUDED.expires_at,
|
|
77
|
+
purchased_at = EXCLUDED.purchased_at,
|
|
78
|
+
will_renew = EXCLUDED.will_renew,
|
|
79
|
+
updated_at = NOW()`, [
|
|
80
|
+
sub.originalTransactionId,
|
|
81
|
+
sub.userId,
|
|
82
|
+
sub.productId,
|
|
83
|
+
sub.platform,
|
|
84
|
+
sub.status,
|
|
85
|
+
sub.expiresAt,
|
|
86
|
+
sub.purchasedAt,
|
|
87
|
+
sub.willRenew,
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns the most recently updated subscription for the given user, or
|
|
92
|
+
* `null` if no record exists.
|
|
93
|
+
*
|
|
94
|
+
* "Most recent" is determined by `updated_at DESC` so that the latest
|
|
95
|
+
* status change is always returned when a user has multiple subscriptions.
|
|
96
|
+
*/
|
|
97
|
+
async getByUserId(userId) {
|
|
98
|
+
const pool = await this.getPool();
|
|
99
|
+
const result = await pool.query(`SELECT *
|
|
100
|
+
FROM onesub_subscriptions
|
|
101
|
+
WHERE user_id = $1
|
|
102
|
+
ORDER BY updated_at DESC
|
|
103
|
+
LIMIT 1`, [userId]);
|
|
104
|
+
return result.rows.length > 0 ? rowToSubscriptionInfo(result.rows[0]) : null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Returns the subscription identified by `originalTransactionId` (the
|
|
108
|
+
* table primary key), or `null` if it does not exist.
|
|
109
|
+
*/
|
|
110
|
+
async getByTransactionId(txId) {
|
|
111
|
+
const pool = await this.getPool();
|
|
112
|
+
const result = await pool.query(`SELECT *
|
|
113
|
+
FROM onesub_subscriptions
|
|
114
|
+
WHERE original_transaction_id = $1`, [txId]);
|
|
115
|
+
return result.rows.length > 0 ? rowToSubscriptionInfo(result.rows[0]) : null;
|
|
116
|
+
}
|
|
117
|
+
/** Gracefully close the underlying connection pool. */
|
|
118
|
+
async close() {
|
|
119
|
+
if (this.poolPromise) {
|
|
120
|
+
const pool = await this.poolPromise;
|
|
121
|
+
await pool.end();
|
|
122
|
+
this.poolPromise = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function rowToSubscriptionInfo(row) {
|
|
127
|
+
return {
|
|
128
|
+
originalTransactionId: row.original_transaction_id,
|
|
129
|
+
userId: row.user_id,
|
|
130
|
+
productId: row.product_id,
|
|
131
|
+
platform: row.platform,
|
|
132
|
+
status: row.status,
|
|
133
|
+
expiresAt: row.expires_at.toISOString(),
|
|
134
|
+
purchasedAt: row.purchased_at.toISOString(),
|
|
135
|
+
willRenew: row.will_renew,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onesub/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Server-side receipt validation middleware for react-native-iap. Apple StoreKit 2 + Google Play Billing. One line.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"start": "node dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react-native-iap",
|
|
16
|
+
"in-app-purchase",
|
|
17
|
+
"receipt-validation",
|
|
18
|
+
"storekit",
|
|
19
|
+
"storekit2",
|
|
20
|
+
"google-play-billing",
|
|
21
|
+
"apple",
|
|
22
|
+
"google",
|
|
23
|
+
"subscription",
|
|
24
|
+
"iap",
|
|
25
|
+
"express",
|
|
26
|
+
"middleware",
|
|
27
|
+
"webhook",
|
|
28
|
+
"server"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/jeonghwanko/onesub.git",
|
|
33
|
+
"directory": "packages/server"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/jeonghwanko/onesub",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@onesub/shared": "0.1.0",
|
|
42
|
+
"express": "^4.19.2",
|
|
43
|
+
"jose": "^5.9.6",
|
|
44
|
+
"zod": "^3.23.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"pg": ">=8.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"pg": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/express": "^4.17.21",
|
|
56
|
+
"@types/node": "^20.17.0",
|
|
57
|
+
"@types/pg": "^8.20.0",
|
|
58
|
+
"typescript": "^5.7.0"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=20"
|
|
62
|
+
}
|
|
63
|
+
}
|