@vex-chat/spire 2.4.0 → 2.6.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/BillingVerification.d.ts +40 -0
- package/dist/BillingVerification.js +535 -0
- package/dist/BillingVerification.js.map +1 -0
- package/dist/Database.d.ts +39 -1
- package/dist/Database.js +285 -2
- package/dist/Database.js.map +1 -1
- package/dist/db/schema.d.ts +50 -0
- package/dist/migrations/2026-06-24_account-entitlements.d.ts +8 -0
- package/dist/migrations/2026-06-24_account-entitlements.js +20 -0
- package/dist/migrations/2026-06-24_account-entitlements.js.map +1 -0
- package/dist/migrations/2026-07-02_store-subscriptions.d.ts +8 -0
- package/dist/migrations/2026-07-02_store-subscriptions.js +83 -0
- package/dist/migrations/2026-07-02_store-subscriptions.js.map +1 -0
- package/dist/server/billing.d.ts +14 -0
- package/dist/server/billing.js +234 -0
- package/dist/server/billing.js.map +1 -0
- package/dist/server/entitlements.d.ts +8 -0
- package/dist/server/entitlements.js +71 -0
- package/dist/server/entitlements.js.map +1 -0
- package/dist/server/index.js +6 -0
- package/dist/server/index.js.map +1 -1
- package/package.json +4 -4
- package/src/BillingVerification.ts +800 -0
- package/src/Database.ts +422 -2
- package/src/__tests__/Database.spec.ts +6 -3
- package/src/__tests__/billing.spec.ts +282 -0
- package/src/__tests__/entitlements.spec.ts +241 -0
- package/src/db/schema.ts +66 -1
- package/src/migrations/2026-06-24_account-entitlements.ts +23 -0
- package/src/migrations/2026-07-02_store-subscriptions.ts +92 -0
- package/src/server/billing.ts +361 -0
- package/src/server/entitlements.ts +104 -0
- package/src/server/index.ts +6 -0
package/dist/db/schema.d.ts
CHANGED
|
@@ -4,6 +4,50 @@
|
|
|
4
4
|
* Commercial licenses available at vex.wtf
|
|
5
5
|
*/
|
|
6
6
|
import type { Insertable, Selectable, Updateable } from "kysely";
|
|
7
|
+
export type AccountEntitlementRow = Selectable<AccountEntitlementsTable>;
|
|
8
|
+
export interface AccountEntitlementsTable {
|
|
9
|
+
expiresAt: null | string;
|
|
10
|
+
source: string;
|
|
11
|
+
tier: string;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
userID: string;
|
|
14
|
+
}
|
|
15
|
+
export type AccountEntitlementUpdate = Updateable<AccountEntitlementsTable>;
|
|
16
|
+
export type BillingStoreSubscriptionRow = Selectable<BillingStoreSubscriptionsTable>;
|
|
17
|
+
export interface BillingStoreSubscriptionsTable {
|
|
18
|
+
createdAt: string;
|
|
19
|
+
environment: string;
|
|
20
|
+
expiresAt: null | string;
|
|
21
|
+
externalOriginalID: null | string;
|
|
22
|
+
externalTransactionID: null | string;
|
|
23
|
+
platform: string;
|
|
24
|
+
productID: string;
|
|
25
|
+
purchaseToken: null | string;
|
|
26
|
+
purchaseTokenHash: null | string;
|
|
27
|
+
rawPayload: string;
|
|
28
|
+
status: string;
|
|
29
|
+
storeProductID: string;
|
|
30
|
+
subscriptionID: string;
|
|
31
|
+
tier: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
userID: string;
|
|
34
|
+
}
|
|
35
|
+
export type BillingStoreSubscriptionUpdate = Updateable<BillingStoreSubscriptionsTable>;
|
|
36
|
+
export type BillingStoreTransactionRow = Selectable<BillingStoreTransactionsTable>;
|
|
37
|
+
export interface BillingStoreTransactionsTable {
|
|
38
|
+
environment: string;
|
|
39
|
+
eventType: string;
|
|
40
|
+
externalTransactionID: null | string;
|
|
41
|
+
platform: string;
|
|
42
|
+
processedAt: string;
|
|
43
|
+
purchaseTokenHash: null | string;
|
|
44
|
+
rawPayload: string;
|
|
45
|
+
storeProductID: string;
|
|
46
|
+
subscriptionID: string;
|
|
47
|
+
transactionID: string;
|
|
48
|
+
userID: string;
|
|
49
|
+
}
|
|
50
|
+
export type BillingStoreTransactionUpdate = Updateable<BillingStoreTransactionsTable>;
|
|
7
51
|
export type ChannelRow = Selectable<ChannelsTable>;
|
|
8
52
|
export interface ChannelsTable {
|
|
9
53
|
channelID: string;
|
|
@@ -68,6 +112,9 @@ export interface MailTable {
|
|
|
68
112
|
time: string;
|
|
69
113
|
}
|
|
70
114
|
export type MailUpdate = Updateable<MailTable>;
|
|
115
|
+
export type NewAccountEntitlement = Insertable<AccountEntitlementsTable>;
|
|
116
|
+
export type NewBillingStoreSubscription = Insertable<BillingStoreSubscriptionsTable>;
|
|
117
|
+
export type NewBillingStoreTransaction = Insertable<BillingStoreTransactionsTable>;
|
|
71
118
|
export type NewChannel = Insertable<ChannelsTable>;
|
|
72
119
|
export type NewDevice = Insertable<DevicesTable>;
|
|
73
120
|
export type NewDevicePasskeyApproval = Insertable<DevicePasskeyApprovalsTable>;
|
|
@@ -141,6 +188,9 @@ export interface PreKeysTable {
|
|
|
141
188
|
}
|
|
142
189
|
export type PreKeyUpdate = Updateable<PreKeysTable>;
|
|
143
190
|
export interface ServerDatabase {
|
|
191
|
+
account_entitlements: AccountEntitlementsTable;
|
|
192
|
+
billing_store_subscriptions: BillingStoreSubscriptionsTable;
|
|
193
|
+
billing_store_transactions: BillingStoreTransactionsTable;
|
|
144
194
|
channels: ChannelsTable;
|
|
145
195
|
device_passkey_approvals: DevicePasskeyApprovalsTable;
|
|
146
196
|
devices: DevicesTable;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
import type { Kysely } from "kysely";
|
|
7
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
8
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
export async function down(db) {
|
|
7
|
+
await db.schema.dropTable("account_entitlements").ifExists().execute();
|
|
8
|
+
}
|
|
9
|
+
export async function up(db) {
|
|
10
|
+
await db.schema
|
|
11
|
+
.createTable("account_entitlements")
|
|
12
|
+
.ifNotExists()
|
|
13
|
+
.addColumn("userID", "varchar(255)", (cb) => cb.primaryKey())
|
|
14
|
+
.addColumn("tier", "varchar(32)", (cb) => cb.notNull())
|
|
15
|
+
.addColumn("source", "varchar(32)", (cb) => cb.notNull())
|
|
16
|
+
.addColumn("expiresAt", "text")
|
|
17
|
+
.addColumn("updatedAt", "text", (cb) => cb.notNull())
|
|
18
|
+
.execute();
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=2026-06-24_account-entitlements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"2026-06-24_account-entitlements.js","sourceRoot":"","sources":["../../src/migrations/2026-06-24_account-entitlements.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,EAAmB;IAC1C,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,EAAmB;IACxC,MAAM,EAAE,CAAC,MAAM;SACV,WAAW,CAAC,sBAAsB,CAAC;SACnC,WAAW,EAAE;SACb,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC;SAC5D,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;SACtD,SAAS,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;SACxD,SAAS,CAAC,WAAW,EAAE,MAAM,CAAC;SAC9B,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;SACpD,OAAO,EAAE,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
import type { Kysely } from "kysely";
|
|
7
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
8
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
export async function down(db) {
|
|
7
|
+
await db.schema
|
|
8
|
+
.dropTable("billing_store_transactions")
|
|
9
|
+
.ifExists()
|
|
10
|
+
.execute();
|
|
11
|
+
await db.schema
|
|
12
|
+
.dropTable("billing_store_subscriptions")
|
|
13
|
+
.ifExists()
|
|
14
|
+
.execute();
|
|
15
|
+
}
|
|
16
|
+
export async function up(db) {
|
|
17
|
+
await db.schema
|
|
18
|
+
.createTable("billing_store_subscriptions")
|
|
19
|
+
.ifNotExists()
|
|
20
|
+
.addColumn("subscriptionID", "varchar(255)", (cb) => cb.primaryKey())
|
|
21
|
+
.addColumn("userID", "varchar(255)", (cb) => cb.notNull())
|
|
22
|
+
.addColumn("platform", "varchar(32)", (cb) => cb.notNull())
|
|
23
|
+
.addColumn("environment", "varchar(32)", (cb) => cb.notNull())
|
|
24
|
+
.addColumn("productID", "varchar(255)", (cb) => cb.notNull())
|
|
25
|
+
.addColumn("storeProductID", "varchar(255)", (cb) => cb.notNull())
|
|
26
|
+
.addColumn("tier", "varchar(32)", (cb) => cb.notNull())
|
|
27
|
+
.addColumn("status", "varchar(32)", (cb) => cb.notNull())
|
|
28
|
+
.addColumn("expiresAt", "text")
|
|
29
|
+
.addColumn("externalOriginalID", "varchar(255)")
|
|
30
|
+
.addColumn("externalTransactionID", "varchar(255)")
|
|
31
|
+
.addColumn("purchaseToken", "text")
|
|
32
|
+
.addColumn("purchaseTokenHash", "varchar(128)")
|
|
33
|
+
.addColumn("rawPayload", "text", (cb) => cb.notNull())
|
|
34
|
+
.addColumn("createdAt", "text", (cb) => cb.notNull())
|
|
35
|
+
.addColumn("updatedAt", "text", (cb) => cb.notNull())
|
|
36
|
+
.execute();
|
|
37
|
+
await db.schema
|
|
38
|
+
.createIndex("billing_store_subscriptions_user_idx")
|
|
39
|
+
.ifNotExists()
|
|
40
|
+
.on("billing_store_subscriptions")
|
|
41
|
+
.column("userID")
|
|
42
|
+
.execute();
|
|
43
|
+
await db.schema
|
|
44
|
+
.createIndex("billing_store_subscriptions_original_idx")
|
|
45
|
+
.ifNotExists()
|
|
46
|
+
.on("billing_store_subscriptions")
|
|
47
|
+
.columns(["platform", "environment", "externalOriginalID"])
|
|
48
|
+
.execute();
|
|
49
|
+
await db.schema
|
|
50
|
+
.createIndex("billing_store_subscriptions_token_hash_idx")
|
|
51
|
+
.ifNotExists()
|
|
52
|
+
.on("billing_store_subscriptions")
|
|
53
|
+
.columns(["platform", "environment", "purchaseTokenHash"])
|
|
54
|
+
.execute();
|
|
55
|
+
await db.schema
|
|
56
|
+
.createTable("billing_store_transactions")
|
|
57
|
+
.ifNotExists()
|
|
58
|
+
.addColumn("transactionID", "varchar(255)", (cb) => cb.primaryKey())
|
|
59
|
+
.addColumn("subscriptionID", "varchar(255)", (cb) => cb.notNull())
|
|
60
|
+
.addColumn("userID", "varchar(255)", (cb) => cb.notNull())
|
|
61
|
+
.addColumn("platform", "varchar(32)", (cb) => cb.notNull())
|
|
62
|
+
.addColumn("environment", "varchar(32)", (cb) => cb.notNull())
|
|
63
|
+
.addColumn("storeProductID", "varchar(255)", (cb) => cb.notNull())
|
|
64
|
+
.addColumn("eventType", "varchar(64)", (cb) => cb.notNull())
|
|
65
|
+
.addColumn("externalTransactionID", "varchar(255)")
|
|
66
|
+
.addColumn("purchaseTokenHash", "varchar(128)")
|
|
67
|
+
.addColumn("rawPayload", "text", (cb) => cb.notNull())
|
|
68
|
+
.addColumn("processedAt", "text", (cb) => cb.notNull())
|
|
69
|
+
.execute();
|
|
70
|
+
await db.schema
|
|
71
|
+
.createIndex("billing_store_transactions_subscription_idx")
|
|
72
|
+
.ifNotExists()
|
|
73
|
+
.on("billing_store_transactions")
|
|
74
|
+
.column("subscriptionID")
|
|
75
|
+
.execute();
|
|
76
|
+
await db.schema
|
|
77
|
+
.createIndex("billing_store_transactions_user_idx")
|
|
78
|
+
.ifNotExists()
|
|
79
|
+
.on("billing_store_transactions")
|
|
80
|
+
.column("userID")
|
|
81
|
+
.execute();
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=2026-07-02_store-subscriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"2026-07-02_store-subscriptions.js","sourceRoot":"","sources":["../../src/migrations/2026-07-02_store-subscriptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
import type { Database } from "../Database.ts";
|
|
7
|
+
export declare function devBillingGrantRoutesEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
8
|
+
export declare const getBillingRouter: (db: Database, notify: (userID: string, event: string, transmissionID: string, data?: unknown, deviceID?: string) => void) => import("express-serve-static-core").Router;
|
|
9
|
+
export declare function billingAccountPath(): string;
|
|
10
|
+
export declare function billingAppleTransactionPath(): string;
|
|
11
|
+
export declare function billingDevGrantPath(): string;
|
|
12
|
+
export declare function billingGooglePurchasePath(): string;
|
|
13
|
+
export declare function billingProductsPath(): string;
|
|
14
|
+
export declare function billingUserPath(userID: string): string;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
import express from "express";
|
|
7
|
+
import { AccountTierSchema, AppleServerNotificationRequestSchema, AppleTransactionVerificationRequestSchema, datetime, GooglePlayDeveloperNotificationRequestSchema, GooglePurchaseVerificationRequestSchema, } from "@vex-chat/types";
|
|
8
|
+
import { z } from "zod/v4";
|
|
9
|
+
import { BillingVerificationError, decodeAppleServerNotificationPayload, decodeGooglePlayPubSubNotificationPayload, getBillingProductCatalog, resolveBillingProduct, verifyAppleTransaction, verifyGooglePurchase, } from "../BillingVerification.js";
|
|
10
|
+
import { devApiKeyMatches } from "./rateLimit.js";
|
|
11
|
+
import { getUser } from "./utils.js";
|
|
12
|
+
import { sendWireResponse } from "./wireResponse.js";
|
|
13
|
+
import { protect } from "./index.js";
|
|
14
|
+
const devGrantPayload = z.object({
|
|
15
|
+
expiresAt: datetime.nullable().optional(),
|
|
16
|
+
tier: AccountTierSchema,
|
|
17
|
+
userID: z.string().min(1),
|
|
18
|
+
});
|
|
19
|
+
export function devBillingGrantRoutesEnabled(env = process.env) {
|
|
20
|
+
return (env["NODE_ENV"] !== "production" &&
|
|
21
|
+
env["VEX_ENABLE_DEV_BILLING_GRANTS"] === "1" &&
|
|
22
|
+
(env["DEV_API_KEY"]?.trim().length ?? 0) > 0);
|
|
23
|
+
}
|
|
24
|
+
export const getBillingRouter = (db, notify) => {
|
|
25
|
+
const router = express.Router();
|
|
26
|
+
router.get("/billing/products", protect, (_req, res) => {
|
|
27
|
+
sendWireResponse(_req, res, getBillingProductCatalog());
|
|
28
|
+
});
|
|
29
|
+
router.get("/billing/account", protect, async (req, res) => {
|
|
30
|
+
const userID = getUser(req).userID;
|
|
31
|
+
const state = await db.retrieveBillingAccountState(userID);
|
|
32
|
+
sendWireResponse(req, res, state);
|
|
33
|
+
});
|
|
34
|
+
router.post("/billing/apple/transactions", protect, async (req, res) => {
|
|
35
|
+
const parsed = AppleTransactionVerificationRequestSchema.safeParse(req.body);
|
|
36
|
+
if (!parsed.success) {
|
|
37
|
+
res.status(400).json({
|
|
38
|
+
error: "Invalid Apple transaction payload",
|
|
39
|
+
issues: parsed.error.issues,
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await handleVerifiedPurchase(req, res, {
|
|
44
|
+
db,
|
|
45
|
+
eventType: "apple_transaction_verified",
|
|
46
|
+
notify,
|
|
47
|
+
purchase: await verifyAppleTransaction(parsed.data),
|
|
48
|
+
userID: getUser(req).userID,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
router.post("/billing/google/purchases", protect, async (req, res) => {
|
|
52
|
+
const parsed = GooglePurchaseVerificationRequestSchema.safeParse(req.body);
|
|
53
|
+
if (!parsed.success) {
|
|
54
|
+
res.status(400).json({
|
|
55
|
+
error: "Invalid Google Play purchase payload",
|
|
56
|
+
issues: parsed.error.issues,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
await handleVerifiedPurchase(req, res, {
|
|
61
|
+
db,
|
|
62
|
+
eventType: "google_purchase_verified",
|
|
63
|
+
notify,
|
|
64
|
+
purchase: await verifyGooglePurchase(parsed.data),
|
|
65
|
+
userID: getUser(req).userID,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
router.post("/billing/webhooks/apple", async (req, res) => {
|
|
69
|
+
const parsed = AppleServerNotificationRequestSchema.safeParse(req.body);
|
|
70
|
+
if (!parsed.success) {
|
|
71
|
+
res.status(400).json({
|
|
72
|
+
error: "Invalid Apple server notification payload",
|
|
73
|
+
issues: parsed.error.issues,
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const notification = decodeAppleServerNotificationPayload(parsed.data.signedPayload);
|
|
78
|
+
const signedTransactionInfo = notification.data?.signedTransactionInfo;
|
|
79
|
+
if (!signedTransactionInfo) {
|
|
80
|
+
res.sendStatus(202);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const purchase = await verifyAppleTransaction({
|
|
84
|
+
signedTransactionInfo,
|
|
85
|
+
});
|
|
86
|
+
const userID = await db.retrieveStoreSubscriptionOwner({
|
|
87
|
+
environment: purchase.environment,
|
|
88
|
+
externalOriginalID: purchase.externalOriginalID,
|
|
89
|
+
platform: purchase.platform,
|
|
90
|
+
});
|
|
91
|
+
if (!userID) {
|
|
92
|
+
res.sendStatus(202);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await handleVerifiedPurchase(req, res, {
|
|
96
|
+
db,
|
|
97
|
+
eventType: `apple_webhook_${notification.notificationType ?? "unknown"}`,
|
|
98
|
+
notify,
|
|
99
|
+
purchase,
|
|
100
|
+
userID,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
router.post("/billing/webhooks/google", async (req, res) => {
|
|
104
|
+
const parsed = GooglePlayDeveloperNotificationRequestSchema.safeParse(req.body);
|
|
105
|
+
if (!parsed.success) {
|
|
106
|
+
res.status(400).json({
|
|
107
|
+
error: "Invalid Google Play notification payload",
|
|
108
|
+
issues: parsed.error.issues,
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const notification = decodeGooglePlayPubSubNotificationPayload(req.body);
|
|
113
|
+
const purchaseToken = notification.subscriptionNotification?.purchaseToken;
|
|
114
|
+
if (!purchaseToken) {
|
|
115
|
+
res.sendStatus(202);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const purchase = await verifyGooglePurchase({
|
|
119
|
+
packageName: notification.packageName,
|
|
120
|
+
productID: notification.subscriptionNotification?.subscriptionId,
|
|
121
|
+
purchaseToken,
|
|
122
|
+
});
|
|
123
|
+
const userID = await db.retrieveStoreSubscriptionOwner({
|
|
124
|
+
environment: purchase.environment,
|
|
125
|
+
platform: purchase.platform,
|
|
126
|
+
purchaseToken,
|
|
127
|
+
});
|
|
128
|
+
if (!userID) {
|
|
129
|
+
res.sendStatus(202);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
await handleVerifiedPurchase(req, res, {
|
|
133
|
+
db,
|
|
134
|
+
eventType: "google_webhook_subscription",
|
|
135
|
+
notify,
|
|
136
|
+
purchase,
|
|
137
|
+
userID,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
if (devBillingGrantRoutesEnabled()) {
|
|
141
|
+
router.post("/__dev/billing/grants", async (req, res) => {
|
|
142
|
+
if (!devBillingGrantRoutesEnabled()) {
|
|
143
|
+
res.sendStatus(404);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!devApiKeyMatches(req)) {
|
|
147
|
+
res.sendStatus(403);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const parsed = devGrantPayload.safeParse(req.body);
|
|
151
|
+
if (!parsed.success) {
|
|
152
|
+
res.status(400).json({
|
|
153
|
+
error: "Invalid dev billing grant payload",
|
|
154
|
+
issues: parsed.error.issues,
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const entitlements = await db.setAccountEntitlementTier(parsed.data.userID, parsed.data.tier, {
|
|
159
|
+
expiresAt: parsed.data.expiresAt ?? null,
|
|
160
|
+
source: "store",
|
|
161
|
+
});
|
|
162
|
+
notify(parsed.data.userID, "accountEntitlementsChanged", crypto.randomUUID(), { tier: entitlements.tier });
|
|
163
|
+
sendWireResponse(req, res, entitlements);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
router.use((err, _req, res, next) => {
|
|
167
|
+
if (err instanceof BillingVerificationError) {
|
|
168
|
+
res.status(err.status).json({ error: err.message });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
next(err);
|
|
172
|
+
});
|
|
173
|
+
return router;
|
|
174
|
+
};
|
|
175
|
+
export function billingAccountPath() {
|
|
176
|
+
return "/billing/account";
|
|
177
|
+
}
|
|
178
|
+
export function billingAppleTransactionPath() {
|
|
179
|
+
return "/billing/apple/transactions";
|
|
180
|
+
}
|
|
181
|
+
export function billingDevGrantPath() {
|
|
182
|
+
return "/__dev/billing/grants";
|
|
183
|
+
}
|
|
184
|
+
export function billingGooglePurchasePath() {
|
|
185
|
+
return "/billing/google/purchases";
|
|
186
|
+
}
|
|
187
|
+
export function billingProductsPath() {
|
|
188
|
+
return "/billing/products";
|
|
189
|
+
}
|
|
190
|
+
export function billingUserPath(userID) {
|
|
191
|
+
return `/user/${encodeURIComponent(userID)}/billing`;
|
|
192
|
+
}
|
|
193
|
+
async function handleVerifiedPurchase(req, res, args) {
|
|
194
|
+
const product = resolveBillingProduct(args.purchase);
|
|
195
|
+
const subscription = await persistVerifiedPurchase(args.db, {
|
|
196
|
+
product,
|
|
197
|
+
purchase: args.purchase,
|
|
198
|
+
userID: args.userID,
|
|
199
|
+
});
|
|
200
|
+
await args.db.recordStoreTransaction({
|
|
201
|
+
eventType: args.eventType,
|
|
202
|
+
externalTransactionID: args.purchase.externalTransactionID,
|
|
203
|
+
rawPayload: args.purchase.rawPayload,
|
|
204
|
+
subscriptionID: subscription.subscriptionID,
|
|
205
|
+
userID: args.userID,
|
|
206
|
+
});
|
|
207
|
+
const entitlements = await args.db.recalculateStoreEntitlements(args.userID);
|
|
208
|
+
args.notify(args.userID, "accountEntitlementsChanged", crypto.randomUUID(), {
|
|
209
|
+
tier: entitlements.tier,
|
|
210
|
+
});
|
|
211
|
+
const state = {
|
|
212
|
+
entitlements,
|
|
213
|
+
subscriptions: await args.db.retrieveBillingSubscriptions(args.userID),
|
|
214
|
+
};
|
|
215
|
+
sendWireResponse(req, res, state);
|
|
216
|
+
}
|
|
217
|
+
async function persistVerifiedPurchase(db, args) {
|
|
218
|
+
const input = {
|
|
219
|
+
environment: args.purchase.environment,
|
|
220
|
+
expiresAt: args.purchase.expiresAt,
|
|
221
|
+
externalOriginalID: args.purchase.externalOriginalID,
|
|
222
|
+
externalTransactionID: args.purchase.externalTransactionID,
|
|
223
|
+
platform: args.purchase.platform,
|
|
224
|
+
productID: args.product.productID,
|
|
225
|
+
purchaseToken: args.purchase.purchaseToken,
|
|
226
|
+
rawPayload: args.purchase.rawPayload,
|
|
227
|
+
status: args.purchase.status,
|
|
228
|
+
storeProductID: args.product.storeProductID,
|
|
229
|
+
tier: args.product.tier,
|
|
230
|
+
userID: args.userID,
|
|
231
|
+
};
|
|
232
|
+
return db.upsertStoreSubscription(input);
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=billing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../../src/server/billing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
import type { Database } from "../Database.ts";
|
|
7
|
+
export declare function devEntitlementRoutesEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
8
|
+
export declare const getEntitlementRouter: (db: Database, notify: (userID: string, event: string, transmissionID: string, data?: unknown, deviceID?: string) => void) => import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-2026 Vex Heavy Industries LLC
|
|
3
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
4
|
+
* Commercial licenses available at vex.wtf
|
|
5
|
+
*/
|
|
6
|
+
import express from "express";
|
|
7
|
+
import { AccountTierSchema, datetime } from "@vex-chat/types";
|
|
8
|
+
import { z } from "zod/v4";
|
|
9
|
+
import { devApiKeyMatches } from "./rateLimit.js";
|
|
10
|
+
import { getParam, getUser } from "./utils.js";
|
|
11
|
+
import { sendWireResponse } from "./wireResponse.js";
|
|
12
|
+
import { protect } from "./index.js";
|
|
13
|
+
const devEntitlementPatchPayload = z.object({
|
|
14
|
+
expiresAt: datetime.nullable().optional(),
|
|
15
|
+
tier: AccountTierSchema,
|
|
16
|
+
});
|
|
17
|
+
export function devEntitlementRoutesEnabled(env = process.env) {
|
|
18
|
+
return (env["NODE_ENV"] !== "production" &&
|
|
19
|
+
env["VEX_ENABLE_DEV_ENTITLEMENTS"] === "1" &&
|
|
20
|
+
(env["DEV_API_KEY"]?.trim().length ?? 0) > 0);
|
|
21
|
+
}
|
|
22
|
+
export const getEntitlementRouter = (db, notify) => {
|
|
23
|
+
const router = express.Router();
|
|
24
|
+
router.get("/user/:id/entitlements", protect, async (req, res) => {
|
|
25
|
+
const userDetails = getUser(req);
|
|
26
|
+
const userID = getParam(req, "id");
|
|
27
|
+
if (userDetails.userID !== userID) {
|
|
28
|
+
res.sendStatus(401);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const entitlements = await db.retrieveAccountEntitlements(userID);
|
|
32
|
+
sendWireResponse(req, res, entitlements);
|
|
33
|
+
});
|
|
34
|
+
if (!devEntitlementRoutesEnabled()) {
|
|
35
|
+
return router;
|
|
36
|
+
}
|
|
37
|
+
router.patch("/__dev/user/:id/entitlements", protect, async (req, res) => {
|
|
38
|
+
if (!devEntitlementRoutesEnabled()) {
|
|
39
|
+
res.sendStatus(404);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const userDetails = getUser(req);
|
|
43
|
+
const userID = getParam(req, "id");
|
|
44
|
+
if (userDetails.userID !== userID) {
|
|
45
|
+
res.sendStatus(401);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!devApiKeyMatches(req)) {
|
|
49
|
+
res.sendStatus(403);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const parsed = devEntitlementPatchPayload.safeParse(req.body);
|
|
53
|
+
if (!parsed.success) {
|
|
54
|
+
res.status(400).json({
|
|
55
|
+
error: "Invalid entitlement payload",
|
|
56
|
+
issues: parsed.error.issues,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const entitlements = await db.setAccountEntitlementTier(userID, parsed.data.tier, {
|
|
61
|
+
expiresAt: parsed.data.expiresAt ?? null,
|
|
62
|
+
source: "dev_override",
|
|
63
|
+
});
|
|
64
|
+
notify(userID, "accountEntitlementsChanged", crypto.randomUUID(), {
|
|
65
|
+
tier: entitlements.tier,
|
|
66
|
+
});
|
|
67
|
+
sendWireResponse(req, res, entitlements);
|
|
68
|
+
});
|
|
69
|
+
return router;
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=entitlements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlements.js","sourceRoot":"","sources":["../../src/server/entitlements.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.js
CHANGED
|
@@ -23,7 +23,9 @@ import { msgpack } from "../utils/msgpack.js";
|
|
|
23
23
|
import { verifyPreKeyWsSignature } from "../utils/preKeySignature.js";
|
|
24
24
|
import { spireXSignOpenAsync } from "../utils/spireXSignOpenAsync.js";
|
|
25
25
|
import { getAvatarRouter } from "./avatar.js";
|
|
26
|
+
import { getBillingRouter } from "./billing.js";
|
|
26
27
|
import { getCliPasskeyPageRouter } from "./cliPasskeyPage.js";
|
|
28
|
+
import { getEntitlementRouter } from "./entitlements.js";
|
|
27
29
|
import { errorHandler } from "./errors.js";
|
|
28
30
|
import { getFileRouter } from "./file.js";
|
|
29
31
|
import { getInviteRouter } from "./invite.js";
|
|
@@ -198,6 +200,8 @@ export const initApp = (api, db, tokenValidator, signKeys, notify, disconnectDev
|
|
|
198
200
|
const userRouter = getUserRouter(db, tokenValidator, notify);
|
|
199
201
|
const fileRouter = getFileRouter(db);
|
|
200
202
|
const avatarRouter = getAvatarRouter();
|
|
203
|
+
const billingRouter = getBillingRouter(db, notify);
|
|
204
|
+
const entitlementRouter = getEntitlementRouter(db, notify);
|
|
201
205
|
const inviteRouter = getInviteRouter(db, tokenValidator, notify);
|
|
202
206
|
const passkeyRouter = getPasskeyRouter(db);
|
|
203
207
|
const passkeyDeviceRouter = getPasskeyDeviceRouter(db, notify, disconnectDevices);
|
|
@@ -776,6 +780,8 @@ export const initApp = (api, db, tokenValidator, signKeys, notify, disconnectDev
|
|
|
776
780
|
// an authenticated device) and `/auth/passkey/...` (public
|
|
777
781
|
// sign-in). The router itself defines the full path on each
|
|
778
782
|
// route handler.
|
|
783
|
+
api.use(billingRouter);
|
|
784
|
+
api.use(entitlementRouter);
|
|
779
785
|
api.use(passkeyRouter);
|
|
780
786
|
api.use(passkeyDeviceRouter);
|
|
781
787
|
api.use("/user", userRouter);
|