@bonginkan/maria 4.3.31 → 4.3.33
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/README.md +4 -4
- package/dist/READY.manifest.json +1 -1
- package/dist/bin/maria.cjs +135 -60
- package/dist/bin/maria.cjs.map +1 -1
- package/dist/cli.cjs +135 -60
- package/dist/cli.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/server/express-server.cjs +953 -18
- package/dist/server/express-server.js +953 -18
- package/dist/server-express.cjs +953 -18
- package/dist/server-express.cjs.map +1 -1
- package/package.json +2 -2
- package/src/slash-commands/READY.manifest.json +1 -1
|
@@ -5158,22 +5158,22 @@ var init_from = __esm({
|
|
|
5158
5158
|
init_file();
|
|
5159
5159
|
init_fetch_blob();
|
|
5160
5160
|
({ stat } = fs.promises);
|
|
5161
|
-
blobFromSync = (
|
|
5162
|
-
blobFrom = (
|
|
5163
|
-
fileFrom = (
|
|
5164
|
-
fileFromSync = (
|
|
5165
|
-
fromBlob = (stat2,
|
|
5166
|
-
path:
|
|
5161
|
+
blobFromSync = (path4, type) => fromBlob(fs.statSync(path4), path4, type);
|
|
5162
|
+
blobFrom = (path4, type) => stat(path4).then((stat2) => fromBlob(stat2, path4, type));
|
|
5163
|
+
fileFrom = (path4, type) => stat(path4).then((stat2) => fromFile(stat2, path4, type));
|
|
5164
|
+
fileFromSync = (path4, type) => fromFile(fs.statSync(path4), path4, type);
|
|
5165
|
+
fromBlob = (stat2, path4, type = "") => new fetch_blob_default([new BlobDataItem({
|
|
5166
|
+
path: path4,
|
|
5167
5167
|
size: stat2.size,
|
|
5168
5168
|
lastModified: stat2.mtimeMs,
|
|
5169
5169
|
start: 0
|
|
5170
5170
|
})], { type });
|
|
5171
|
-
fromFile = (stat2,
|
|
5172
|
-
path:
|
|
5171
|
+
fromFile = (stat2, path4, type = "") => new file_default([new BlobDataItem({
|
|
5172
|
+
path: path4,
|
|
5173
5173
|
size: stat2.size,
|
|
5174
5174
|
lastModified: stat2.mtimeMs,
|
|
5175
5175
|
start: 0
|
|
5176
|
-
})], path.basename(
|
|
5176
|
+
})], path.basename(path4), { type, lastModified: stat2.mtimeMs });
|
|
5177
5177
|
BlobDataItem = class _BlobDataItem {
|
|
5178
5178
|
#path;
|
|
5179
5179
|
#start;
|
|
@@ -6844,11 +6844,11 @@ function getRateLimitConfig(endpoint, plan) {
|
|
|
6844
6844
|
const key = `${endpoint}:${plan.toUpperCase()}`;
|
|
6845
6845
|
return RATE_LIMITS[key] || RATE_LIMITS.default;
|
|
6846
6846
|
}
|
|
6847
|
-
function getEndpointCategory(
|
|
6848
|
-
if (
|
|
6849
|
-
if (
|
|
6850
|
-
if (
|
|
6851
|
-
if (
|
|
6847
|
+
function getEndpointCategory(path4) {
|
|
6848
|
+
if (path4.includes("/image")) return "/image";
|
|
6849
|
+
if (path4.includes("/video")) return "/video";
|
|
6850
|
+
if (path4.includes("/code")) return "/code";
|
|
6851
|
+
if (path4.includes("/chat")) return "/chat";
|
|
6852
6852
|
return "default";
|
|
6853
6853
|
}
|
|
6854
6854
|
async function rateLimitMiddleware(req, res, next) {
|
|
@@ -8580,11 +8580,921 @@ I can still help summarize, clarify, or suggest next steps. Try again in a momen
|
|
|
8580
8580
|
}
|
|
8581
8581
|
};
|
|
8582
8582
|
var IMSFacade_default = IMSFacade;
|
|
8583
|
+
var DEFAULT_STATE = {
|
|
8584
|
+
events: {},
|
|
8585
|
+
customers: {},
|
|
8586
|
+
subscriptions: {},
|
|
8587
|
+
invoices: {},
|
|
8588
|
+
entitlements: {},
|
|
8589
|
+
usage: {}
|
|
8590
|
+
};
|
|
8591
|
+
var SubscriptionStore = class {
|
|
8592
|
+
filePath;
|
|
8593
|
+
state = null;
|
|
8594
|
+
loadPromise = null;
|
|
8595
|
+
queue = Promise.resolve();
|
|
8596
|
+
constructor(filePath) {
|
|
8597
|
+
this.filePath = filePath ?? path__namespace.default.resolve(process.cwd(), "data", "subscription-state.json");
|
|
8598
|
+
}
|
|
8599
|
+
async ensureLoaded() {
|
|
8600
|
+
if (this.state) {
|
|
8601
|
+
return;
|
|
8602
|
+
}
|
|
8603
|
+
if (!this.loadPromise) {
|
|
8604
|
+
this.loadPromise = this.loadStateFromDisk();
|
|
8605
|
+
}
|
|
8606
|
+
await this.loadPromise;
|
|
8607
|
+
}
|
|
8608
|
+
async loadStateFromDisk() {
|
|
8609
|
+
try {
|
|
8610
|
+
const raw = await fsp__namespace.default.readFile(this.filePath, "utf8");
|
|
8611
|
+
const parsed = JSON.parse(raw);
|
|
8612
|
+
this.state = {
|
|
8613
|
+
events: parsed.events ?? {},
|
|
8614
|
+
customers: parsed.customers ?? {},
|
|
8615
|
+
subscriptions: parsed.subscriptions ?? {},
|
|
8616
|
+
invoices: parsed.invoices ?? {},
|
|
8617
|
+
entitlements: parsed.entitlements ?? {},
|
|
8618
|
+
usage: parsed.usage ?? {}
|
|
8619
|
+
};
|
|
8620
|
+
} catch (error) {
|
|
8621
|
+
if (error?.code === "ENOENT") {
|
|
8622
|
+
this.state = { ...DEFAULT_STATE };
|
|
8623
|
+
await this.persist();
|
|
8624
|
+
return;
|
|
8625
|
+
}
|
|
8626
|
+
throw error;
|
|
8627
|
+
}
|
|
8628
|
+
}
|
|
8629
|
+
async persist() {
|
|
8630
|
+
if (!this.state) {
|
|
8631
|
+
return;
|
|
8632
|
+
}
|
|
8633
|
+
const directory = path__namespace.default.dirname(this.filePath);
|
|
8634
|
+
await fsp__namespace.default.mkdir(directory, { recursive: true });
|
|
8635
|
+
const payload = JSON.stringify(this.state, null, 2);
|
|
8636
|
+
await fsp__namespace.default.writeFile(this.filePath, payload, "utf8");
|
|
8637
|
+
}
|
|
8638
|
+
runExclusive(task) {
|
|
8639
|
+
const result = this.queue.then(task);
|
|
8640
|
+
this.queue = result.then(() => void 0).catch(() => void 0);
|
|
8641
|
+
return result;
|
|
8642
|
+
}
|
|
8643
|
+
async markEventReceived(event) {
|
|
8644
|
+
return this.runExclusive(async () => {
|
|
8645
|
+
await this.ensureLoaded();
|
|
8646
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8647
|
+
const state = this.state;
|
|
8648
|
+
const existing = state.events[event.id];
|
|
8649
|
+
if (!existing) {
|
|
8650
|
+
state.events[event.id] = {
|
|
8651
|
+
id: event.id,
|
|
8652
|
+
type: event.type,
|
|
8653
|
+
status: "received",
|
|
8654
|
+
receivedAt: now,
|
|
8655
|
+
stripeCreatedAt: event.created ? new Date(event.created * 1e3).toISOString() : void 0
|
|
8656
|
+
};
|
|
8657
|
+
await this.persist();
|
|
8658
|
+
return "new";
|
|
8659
|
+
}
|
|
8660
|
+
if (existing.status === "processed") {
|
|
8661
|
+
return "duplicate";
|
|
8662
|
+
}
|
|
8663
|
+
state.events[event.id] = {
|
|
8664
|
+
...existing,
|
|
8665
|
+
type: event.type,
|
|
8666
|
+
status: existing.status === "failed" ? "received" : existing.status,
|
|
8667
|
+
receivedAt: existing.receivedAt ?? now
|
|
8668
|
+
};
|
|
8669
|
+
await this.persist();
|
|
8670
|
+
return "pending";
|
|
8671
|
+
});
|
|
8672
|
+
}
|
|
8673
|
+
async markEventProcessed(eventId) {
|
|
8674
|
+
await this.runExclusive(async () => {
|
|
8675
|
+
await this.ensureLoaded();
|
|
8676
|
+
const state = this.state;
|
|
8677
|
+
const existing = state.events[eventId];
|
|
8678
|
+
if (!existing) {
|
|
8679
|
+
return;
|
|
8680
|
+
}
|
|
8681
|
+
state.events[eventId] = {
|
|
8682
|
+
...existing,
|
|
8683
|
+
status: "processed",
|
|
8684
|
+
processedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8685
|
+
error: void 0
|
|
8686
|
+
};
|
|
8687
|
+
await this.persist();
|
|
8688
|
+
});
|
|
8689
|
+
}
|
|
8690
|
+
async markEventFailed(eventId, error) {
|
|
8691
|
+
await this.runExclusive(async () => {
|
|
8692
|
+
await this.ensureLoaded();
|
|
8693
|
+
const state = this.state;
|
|
8694
|
+
const existing = state.events[eventId];
|
|
8695
|
+
if (!existing) {
|
|
8696
|
+
return;
|
|
8697
|
+
}
|
|
8698
|
+
state.events[eventId] = {
|
|
8699
|
+
...existing,
|
|
8700
|
+
status: "failed",
|
|
8701
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8702
|
+
};
|
|
8703
|
+
await this.persist();
|
|
8704
|
+
});
|
|
8705
|
+
}
|
|
8706
|
+
async upsertCustomer(record) {
|
|
8707
|
+
return this.runExclusive(async () => {
|
|
8708
|
+
await this.ensureLoaded();
|
|
8709
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8710
|
+
const state = this.state;
|
|
8711
|
+
const existing = state.customers[record.id];
|
|
8712
|
+
const createdAt = existing?.createdAt ?? record.createdAt ?? now;
|
|
8713
|
+
const nextRecord = {
|
|
8714
|
+
id: record.id,
|
|
8715
|
+
uid: record.uid ?? existing?.uid,
|
|
8716
|
+
email: record.email ?? existing?.email,
|
|
8717
|
+
metadata: {
|
|
8718
|
+
...existing?.metadata ?? {},
|
|
8719
|
+
...record.metadata ?? {}
|
|
8720
|
+
},
|
|
8721
|
+
createdAt,
|
|
8722
|
+
updatedAt: record.updatedAt ?? now
|
|
8723
|
+
};
|
|
8724
|
+
state.customers[record.id] = nextRecord;
|
|
8725
|
+
await this.persist();
|
|
8726
|
+
return nextRecord;
|
|
8727
|
+
});
|
|
8728
|
+
}
|
|
8729
|
+
async getCustomer(customerId) {
|
|
8730
|
+
await this.ensureLoaded();
|
|
8731
|
+
const state = this.state;
|
|
8732
|
+
const record = state.customers[customerId];
|
|
8733
|
+
return record ? { ...record, metadata: record.metadata ? { ...record.metadata } : void 0 } : void 0;
|
|
8734
|
+
}
|
|
8735
|
+
async upsertSubscription(record) {
|
|
8736
|
+
return this.runExclusive(async () => {
|
|
8737
|
+
await this.ensureLoaded();
|
|
8738
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8739
|
+
const state = this.state;
|
|
8740
|
+
const existing = state.subscriptions[record.id];
|
|
8741
|
+
const createdAt = existing?.createdAt ?? record.createdAt ?? now;
|
|
8742
|
+
const nextRecord = {
|
|
8743
|
+
id: record.id,
|
|
8744
|
+
customerId: record.customerId,
|
|
8745
|
+
uid: record.uid ?? existing?.uid,
|
|
8746
|
+
status: record.status,
|
|
8747
|
+
planId: record.planId,
|
|
8748
|
+
pendingPlanId: record.pendingPlanId !== void 0 ? record.pendingPlanId : existing?.pendingPlanId ?? null,
|
|
8749
|
+
pendingPlanEffectiveAt: record.pendingPlanEffectiveAt !== void 0 ? record.pendingPlanEffectiveAt : existing?.pendingPlanEffectiveAt ?? null,
|
|
8750
|
+
cancelAt: record.cancelAt ?? null,
|
|
8751
|
+
cancelAtPeriodEnd: record.cancelAtPeriodEnd ?? false,
|
|
8752
|
+
canceledAt: record.canceledAt ?? null,
|
|
8753
|
+
currentPeriodStart: record.currentPeriodStart,
|
|
8754
|
+
currentPeriodEnd: record.currentPeriodEnd,
|
|
8755
|
+
trialEnd: record.trialEnd ?? null,
|
|
8756
|
+
latestInvoiceId: record.latestInvoiceId ?? existing?.latestInvoiceId,
|
|
8757
|
+
latestInvoiceStatus: record.latestInvoiceStatus ?? existing?.latestInvoiceStatus,
|
|
8758
|
+
latestInvoiceAmount: record.latestInvoiceAmount ?? existing?.latestInvoiceAmount,
|
|
8759
|
+
metadata: {
|
|
8760
|
+
...existing?.metadata ?? {},
|
|
8761
|
+
...record.metadata ?? {}
|
|
8762
|
+
},
|
|
8763
|
+
createdAt,
|
|
8764
|
+
updatedAt: record.updatedAt ?? now
|
|
8765
|
+
};
|
|
8766
|
+
state.subscriptions[record.id] = nextRecord;
|
|
8767
|
+
await this.persist();
|
|
8768
|
+
return nextRecord;
|
|
8769
|
+
});
|
|
8770
|
+
}
|
|
8771
|
+
async getSubscription(subscriptionId) {
|
|
8772
|
+
await this.ensureLoaded();
|
|
8773
|
+
const state = this.state;
|
|
8774
|
+
const record = state.subscriptions[subscriptionId];
|
|
8775
|
+
if (!record) {
|
|
8776
|
+
return void 0;
|
|
8777
|
+
}
|
|
8778
|
+
return {
|
|
8779
|
+
...record,
|
|
8780
|
+
metadata: record.metadata ? { ...record.metadata } : void 0
|
|
8781
|
+
};
|
|
8782
|
+
}
|
|
8783
|
+
async findSubscriptionByCustomer(customerId) {
|
|
8784
|
+
await this.ensureLoaded();
|
|
8785
|
+
const state = this.state;
|
|
8786
|
+
return Object.values(state.subscriptions).find((sub) => sub.customerId === customerId);
|
|
8787
|
+
}
|
|
8788
|
+
async recordInvoice(record) {
|
|
8789
|
+
return this.runExclusive(async () => {
|
|
8790
|
+
await this.ensureLoaded();
|
|
8791
|
+
const state = this.state;
|
|
8792
|
+
state.invoices[record.id] = { ...record };
|
|
8793
|
+
await this.persist();
|
|
8794
|
+
return state.invoices[record.id];
|
|
8795
|
+
});
|
|
8796
|
+
}
|
|
8797
|
+
async setEntitlements(uid, planId, features) {
|
|
8798
|
+
return this.runExclusive(async () => {
|
|
8799
|
+
await this.ensureLoaded();
|
|
8800
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8801
|
+
const state = this.state;
|
|
8802
|
+
const record = {
|
|
8803
|
+
uid,
|
|
8804
|
+
planId,
|
|
8805
|
+
features: [...features],
|
|
8806
|
+
updatedAt: now
|
|
8807
|
+
};
|
|
8808
|
+
state.entitlements[uid] = record;
|
|
8809
|
+
await this.persist();
|
|
8810
|
+
return record;
|
|
8811
|
+
});
|
|
8812
|
+
}
|
|
8813
|
+
async setUsage(params) {
|
|
8814
|
+
return this.runExclusive(async () => {
|
|
8815
|
+
await this.ensureLoaded();
|
|
8816
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8817
|
+
const state = this.state;
|
|
8818
|
+
const key = `${params.uid}:${params.periodId}`;
|
|
8819
|
+
const existing = state.usage[key];
|
|
8820
|
+
const used = existing?.used ?? {
|
|
8821
|
+
requests: 0,
|
|
8822
|
+
tokens: 0,
|
|
8823
|
+
code: 0,
|
|
8824
|
+
attachment: 0
|
|
8825
|
+
};
|
|
8826
|
+
const remaining = {
|
|
8827
|
+
requests: Math.max(0, params.limits.requests - used.requests),
|
|
8828
|
+
tokens: Math.max(0, params.limits.tokens - used.tokens),
|
|
8829
|
+
code: Math.max(0, params.limits.code - used.code),
|
|
8830
|
+
attachment: Math.max(0, params.limits.attachment - used.attachment)
|
|
8831
|
+
};
|
|
8832
|
+
const record = {
|
|
8833
|
+
uid: params.uid,
|
|
8834
|
+
planId: params.planId,
|
|
8835
|
+
periodId: params.periodId,
|
|
8836
|
+
limits: { ...params.limits },
|
|
8837
|
+
used,
|
|
8838
|
+
remaining,
|
|
8839
|
+
createdAt: existing?.createdAt ?? now,
|
|
8840
|
+
updatedAt: now
|
|
8841
|
+
};
|
|
8842
|
+
state.usage[key] = record;
|
|
8843
|
+
await this.persist();
|
|
8844
|
+
return record;
|
|
8845
|
+
});
|
|
8846
|
+
}
|
|
8847
|
+
async getUsage(uid, periodId) {
|
|
8848
|
+
await this.ensureLoaded();
|
|
8849
|
+
const key = `${uid}:${periodId}`;
|
|
8850
|
+
const record = this.state.usage[key];
|
|
8851
|
+
if (!record) {
|
|
8852
|
+
return void 0;
|
|
8853
|
+
}
|
|
8854
|
+
return {
|
|
8855
|
+
...record,
|
|
8856
|
+
limits: { ...record.limits },
|
|
8857
|
+
used: { ...record.used },
|
|
8858
|
+
remaining: { ...record.remaining }
|
|
8859
|
+
};
|
|
8860
|
+
}
|
|
8861
|
+
async getEntitlements(uid) {
|
|
8862
|
+
await this.ensureLoaded();
|
|
8863
|
+
const record = this.state.entitlements[uid];
|
|
8864
|
+
if (!record) {
|
|
8865
|
+
return void 0;
|
|
8866
|
+
}
|
|
8867
|
+
return {
|
|
8868
|
+
...record,
|
|
8869
|
+
features: [...record.features]
|
|
8870
|
+
};
|
|
8871
|
+
}
|
|
8872
|
+
async snapshot() {
|
|
8873
|
+
await this.ensureLoaded();
|
|
8874
|
+
const state = this.state;
|
|
8875
|
+
return JSON.parse(JSON.stringify(state));
|
|
8876
|
+
}
|
|
8877
|
+
};
|
|
8878
|
+
var subscription_store_default = SubscriptionStore;
|
|
8879
|
+
var PLAN_PRIORITY = {
|
|
8880
|
+
free: 0,
|
|
8881
|
+
starter: 1,
|
|
8882
|
+
pro: 2,
|
|
8883
|
+
ultra: 3
|
|
8884
|
+
};
|
|
8885
|
+
var PLAN_CONFIG = {
|
|
8886
|
+
free: {
|
|
8887
|
+
entitlements: [
|
|
8888
|
+
"core.cli.basic",
|
|
8889
|
+
"ai.chat.basic",
|
|
8890
|
+
"usage.metrics.basic"
|
|
8891
|
+
],
|
|
8892
|
+
quota: {
|
|
8893
|
+
requests: 100,
|
|
8894
|
+
tokens: 5e4,
|
|
8895
|
+
code: 30,
|
|
8896
|
+
attachment: 10
|
|
8897
|
+
}
|
|
8898
|
+
},
|
|
8899
|
+
starter: {
|
|
8900
|
+
entitlements: [
|
|
8901
|
+
"core.cli.basic",
|
|
8902
|
+
"ai.chat.basic",
|
|
8903
|
+
"ai.chat.priority",
|
|
8904
|
+
"usage.metrics.expanded",
|
|
8905
|
+
"workspace.multi-device"
|
|
8906
|
+
],
|
|
8907
|
+
quota: {
|
|
8908
|
+
requests: 500,
|
|
8909
|
+
tokens: 25e4,
|
|
8910
|
+
code: 200,
|
|
8911
|
+
attachment: 50
|
|
8912
|
+
}
|
|
8913
|
+
},
|
|
8914
|
+
pro: {
|
|
8915
|
+
entitlements: [
|
|
8916
|
+
"core.cli.basic",
|
|
8917
|
+
"ai.chat.priority",
|
|
8918
|
+
"ai.code.advanced",
|
|
8919
|
+
"orchestrator.code",
|
|
8920
|
+
"usage.metrics.expanded",
|
|
8921
|
+
"workspace.multi-device",
|
|
8922
|
+
"api.webhooks"
|
|
8923
|
+
],
|
|
8924
|
+
quota: {
|
|
8925
|
+
requests: 2e3,
|
|
8926
|
+
tokens: 1e6,
|
|
8927
|
+
code: 1e3,
|
|
8928
|
+
attachment: 200
|
|
8929
|
+
}
|
|
8930
|
+
},
|
|
8931
|
+
ultra: {
|
|
8932
|
+
entitlements: [
|
|
8933
|
+
"core.cli.basic",
|
|
8934
|
+
"ai.chat.priority",
|
|
8935
|
+
"ai.code.advanced",
|
|
8936
|
+
"orchestrator.code",
|
|
8937
|
+
"orchestrator.media",
|
|
8938
|
+
"monitoring.analytics",
|
|
8939
|
+
"api.webhooks",
|
|
8940
|
+
"support.priority"
|
|
8941
|
+
],
|
|
8942
|
+
quota: {
|
|
8943
|
+
requests: 1e4,
|
|
8944
|
+
tokens: 5e6,
|
|
8945
|
+
code: 5e3,
|
|
8946
|
+
attachment: 1e3
|
|
8947
|
+
}
|
|
8948
|
+
}
|
|
8949
|
+
};
|
|
8950
|
+
var PLAN_ALIAS_MAP = {
|
|
8951
|
+
starterannual: "starter",
|
|
8952
|
+
"starter-annual": "starter",
|
|
8953
|
+
"starter_yearly": "starter",
|
|
8954
|
+
"starter-yearly": "starter",
|
|
8955
|
+
starteryearly: "starter",
|
|
8956
|
+
proannual: "pro",
|
|
8957
|
+
"pro-annual": "pro",
|
|
8958
|
+
"pro_yearly": "pro",
|
|
8959
|
+
"pro-yearly": "pro",
|
|
8960
|
+
proyearly: "pro",
|
|
8961
|
+
ultraannual: "ultra",
|
|
8962
|
+
"ultra-annual": "ultra",
|
|
8963
|
+
"ultra_yearly": "ultra",
|
|
8964
|
+
"ultra-yearly": "ultra",
|
|
8965
|
+
ultrayearly: "ultra"
|
|
8966
|
+
};
|
|
8967
|
+
function sanitizeKey(value) {
|
|
8968
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
8969
|
+
}
|
|
8970
|
+
function toIso(timestamp) {
|
|
8971
|
+
if (!timestamp) {
|
|
8972
|
+
return null;
|
|
8973
|
+
}
|
|
8974
|
+
return new Date(timestamp * 1e3).toISOString();
|
|
8975
|
+
}
|
|
8976
|
+
function formatPeriodId(date) {
|
|
8977
|
+
const instance = typeof date === "number" ? new Date(date) : date;
|
|
8978
|
+
const year = instance.getUTCFullYear();
|
|
8979
|
+
const month = String(instance.getUTCMonth() + 1).padStart(2, "0");
|
|
8980
|
+
return `${year}${month}`;
|
|
8981
|
+
}
|
|
8982
|
+
function extractId(value) {
|
|
8983
|
+
if (!value) {
|
|
8984
|
+
return void 0;
|
|
8985
|
+
}
|
|
8986
|
+
if (typeof value === "string") {
|
|
8987
|
+
return value;
|
|
8988
|
+
}
|
|
8989
|
+
return value.id ?? void 0;
|
|
8990
|
+
}
|
|
8991
|
+
function extractMetadataPlan(metadata) {
|
|
8992
|
+
if (!metadata) {
|
|
8993
|
+
return void 0;
|
|
8994
|
+
}
|
|
8995
|
+
const keys = ["planId", "plan_id", "plan", "plan-id", "plan_name"];
|
|
8996
|
+
for (const key of keys) {
|
|
8997
|
+
const value = metadata[key];
|
|
8998
|
+
if (value && value.trim()) {
|
|
8999
|
+
return value.trim();
|
|
9000
|
+
}
|
|
9001
|
+
}
|
|
9002
|
+
return void 0;
|
|
9003
|
+
}
|
|
9004
|
+
function extractUidFromMetadata(metadata) {
|
|
9005
|
+
if (!metadata) {
|
|
9006
|
+
return void 0;
|
|
9007
|
+
}
|
|
9008
|
+
const keys = ["uid", "userId", "user_id", "customer_uid"];
|
|
9009
|
+
for (const key of keys) {
|
|
9010
|
+
const value = metadata[key];
|
|
9011
|
+
if (value && value.trim()) {
|
|
9012
|
+
return value.trim();
|
|
9013
|
+
}
|
|
9014
|
+
}
|
|
9015
|
+
return void 0;
|
|
9016
|
+
}
|
|
9017
|
+
var StripeWebhookService = class {
|
|
9018
|
+
secret;
|
|
9019
|
+
toleranceSeconds;
|
|
9020
|
+
store;
|
|
9021
|
+
pricePlanMap = /* @__PURE__ */ new Map();
|
|
9022
|
+
productPlanMap = /* @__PURE__ */ new Map();
|
|
9023
|
+
constructor(options) {
|
|
9024
|
+
if (!options.secret) {
|
|
9025
|
+
throw new Error("Stripe webhook secret must be provided");
|
|
9026
|
+
}
|
|
9027
|
+
this.secret = options.secret;
|
|
9028
|
+
this.toleranceSeconds = options.toleranceSeconds ?? 300;
|
|
9029
|
+
this.store = options.store ?? new subscription_store_default();
|
|
9030
|
+
this.registerEnvPlanMappings();
|
|
9031
|
+
if (options.priceIdMap) {
|
|
9032
|
+
for (const [id, plan] of Object.entries(options.priceIdMap)) {
|
|
9033
|
+
this.pricePlanMap.set(sanitizeKey(id), plan);
|
|
9034
|
+
}
|
|
9035
|
+
}
|
|
9036
|
+
if (options.productIdMap) {
|
|
9037
|
+
for (const [id, plan] of Object.entries(options.productIdMap)) {
|
|
9038
|
+
this.productPlanMap.set(sanitizeKey(id), plan);
|
|
9039
|
+
}
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
verifySignature(rawBody, signatureHeader) {
|
|
9043
|
+
if (!signatureHeader) {
|
|
9044
|
+
throw new Error("Stripe signature header is missing");
|
|
9045
|
+
}
|
|
9046
|
+
const header = Array.isArray(signatureHeader) ? signatureHeader.join(",") : signatureHeader;
|
|
9047
|
+
const { timestamp, signatures } = this.parseSignatureHeader(header);
|
|
9048
|
+
if (!timestamp || signatures.length === 0) {
|
|
9049
|
+
throw new Error("Stripe signature header is invalid");
|
|
9050
|
+
}
|
|
9051
|
+
const timestampSeconds = Number(timestamp);
|
|
9052
|
+
if (!Number.isFinite(timestampSeconds)) {
|
|
9053
|
+
throw new Error("Stripe signature timestamp is invalid");
|
|
9054
|
+
}
|
|
9055
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
9056
|
+
if (Math.abs(nowSeconds - timestampSeconds) > this.toleranceSeconds) {
|
|
9057
|
+
throw new Error("Stripe signature timestamp outside of tolerance window");
|
|
9058
|
+
}
|
|
9059
|
+
const signedPayload = `${timestamp}.${rawBody.toString("utf8")}`;
|
|
9060
|
+
const expected = crypto.createHmac("sha256", this.secret).update(signedPayload).digest("hex");
|
|
9061
|
+
const expectedBuffer = Buffer.from(expected, "hex");
|
|
9062
|
+
const isValid = signatures.some((signature) => {
|
|
9063
|
+
const signatureBuffer = Buffer.from(signature, "hex");
|
|
9064
|
+
if (signatureBuffer.length !== expectedBuffer.length) {
|
|
9065
|
+
return false;
|
|
9066
|
+
}
|
|
9067
|
+
return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);
|
|
9068
|
+
});
|
|
9069
|
+
if (!isValid) {
|
|
9070
|
+
throw new Error("Stripe signature verification failed");
|
|
9071
|
+
}
|
|
9072
|
+
}
|
|
9073
|
+
parseEvent(rawBody) {
|
|
9074
|
+
try {
|
|
9075
|
+
const parsed = JSON.parse(rawBody.toString("utf8"));
|
|
9076
|
+
if (!parsed || typeof parsed !== "object" || !parsed.id || !parsed.type) {
|
|
9077
|
+
throw new Error("Invalid event payload");
|
|
9078
|
+
}
|
|
9079
|
+
return parsed;
|
|
9080
|
+
} catch (error) {
|
|
9081
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
9082
|
+
throw new Error(`Failed to parse Stripe event payload: ${message}`);
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
async processEvent(event) {
|
|
9086
|
+
const receipt = await this.store.markEventReceived({
|
|
9087
|
+
id: event.id,
|
|
9088
|
+
type: event.type,
|
|
9089
|
+
created: event.created
|
|
9090
|
+
});
|
|
9091
|
+
if (receipt === "duplicate") {
|
|
9092
|
+
return;
|
|
9093
|
+
}
|
|
9094
|
+
try {
|
|
9095
|
+
switch (event.type) {
|
|
9096
|
+
case "checkout.session.completed":
|
|
9097
|
+
await this.handleCheckoutSession(event);
|
|
9098
|
+
break;
|
|
9099
|
+
case "customer.subscription.created":
|
|
9100
|
+
await this.handleSubscriptionCreated(event);
|
|
9101
|
+
break;
|
|
9102
|
+
case "customer.subscription.updated":
|
|
9103
|
+
await this.handleSubscriptionUpdated(event);
|
|
9104
|
+
break;
|
|
9105
|
+
case "customer.subscription.deleted":
|
|
9106
|
+
await this.handleSubscriptionDeleted(event);
|
|
9107
|
+
break;
|
|
9108
|
+
case "invoice.payment_succeeded":
|
|
9109
|
+
await this.handleInvoicePayment(event, true);
|
|
9110
|
+
break;
|
|
9111
|
+
case "invoice.payment_failed":
|
|
9112
|
+
await this.handleInvoicePayment(event, false);
|
|
9113
|
+
break;
|
|
9114
|
+
default:
|
|
9115
|
+
console.info(`Unhandled Stripe event type: ${event.type}`);
|
|
9116
|
+
}
|
|
9117
|
+
await this.store.markEventProcessed(event.id);
|
|
9118
|
+
} catch (error) {
|
|
9119
|
+
await this.store.markEventFailed(event.id, error);
|
|
9120
|
+
throw error;
|
|
9121
|
+
}
|
|
9122
|
+
}
|
|
9123
|
+
parseSignatureHeader(signatureHeader) {
|
|
9124
|
+
const parts = signatureHeader.split(",");
|
|
9125
|
+
let timestamp;
|
|
9126
|
+
const signatures = [];
|
|
9127
|
+
for (const part of parts) {
|
|
9128
|
+
const [key, value] = part.trim().split("=");
|
|
9129
|
+
if (key === "t") {
|
|
9130
|
+
timestamp = value;
|
|
9131
|
+
} else if (key.startsWith("v")) {
|
|
9132
|
+
signatures.push(value);
|
|
9133
|
+
}
|
|
9134
|
+
}
|
|
9135
|
+
return { timestamp, signatures };
|
|
9136
|
+
}
|
|
9137
|
+
registerEnvPlanMappings() {
|
|
9138
|
+
const mapping = [
|
|
9139
|
+
[process.env.STRIPE_PRICE_STARTER, "starter"],
|
|
9140
|
+
[process.env.STRIPE_PRICE_STARTER_ANNUAL, "starter"],
|
|
9141
|
+
[process.env.STRIPE_PRICE_PRO, "pro"],
|
|
9142
|
+
[process.env.STRIPE_PRICE_PRO_ANNUAL, "pro"],
|
|
9143
|
+
[process.env.STRIPE_PRICE_ULTRA, "ultra"],
|
|
9144
|
+
[process.env.STRIPE_PRICE_ULTRA_ANNUAL, "ultra"]
|
|
9145
|
+
];
|
|
9146
|
+
mapping.forEach(([id, plan]) => {
|
|
9147
|
+
if (id && id.trim()) {
|
|
9148
|
+
this.pricePlanMap.set(sanitizeKey(id), plan);
|
|
9149
|
+
}
|
|
9150
|
+
});
|
|
9151
|
+
const productMapping = [
|
|
9152
|
+
[process.env.STRIPE_PRODUCT_STARTER, "starter"],
|
|
9153
|
+
[process.env.STRIPE_PRODUCT_PRO, "pro"],
|
|
9154
|
+
[process.env.STRIPE_PRODUCT_ULTRA, "ultra"]
|
|
9155
|
+
];
|
|
9156
|
+
productMapping.forEach(([id, plan]) => {
|
|
9157
|
+
if (id && id.trim()) {
|
|
9158
|
+
this.productPlanMap.set(sanitizeKey(id), plan);
|
|
9159
|
+
}
|
|
9160
|
+
});
|
|
9161
|
+
}
|
|
9162
|
+
normalizePlanId(planId) {
|
|
9163
|
+
if (!planId) {
|
|
9164
|
+
return "free";
|
|
9165
|
+
}
|
|
9166
|
+
const key = sanitizeKey(planId);
|
|
9167
|
+
if (PLAN_CONFIG[key]) {
|
|
9168
|
+
return key;
|
|
9169
|
+
}
|
|
9170
|
+
const alias = PLAN_ALIAS_MAP[key];
|
|
9171
|
+
if (alias) {
|
|
9172
|
+
return alias;
|
|
9173
|
+
}
|
|
9174
|
+
return "free";
|
|
9175
|
+
}
|
|
9176
|
+
resolvePlanFromSubscription(subscription) {
|
|
9177
|
+
const metadataPlan = extractMetadataPlan(subscription.metadata);
|
|
9178
|
+
if (metadataPlan) {
|
|
9179
|
+
return this.normalizePlanId(metadataPlan);
|
|
9180
|
+
}
|
|
9181
|
+
const items = subscription.items?.data ?? [];
|
|
9182
|
+
for (const item of items) {
|
|
9183
|
+
const itemMetadataPlan = extractMetadataPlan(item.metadata);
|
|
9184
|
+
if (itemMetadataPlan) {
|
|
9185
|
+
return this.normalizePlanId(itemMetadataPlan);
|
|
9186
|
+
}
|
|
9187
|
+
const priceMetadataPlan = extractMetadataPlan(item.price?.metadata);
|
|
9188
|
+
if (priceMetadataPlan) {
|
|
9189
|
+
return this.normalizePlanId(priceMetadataPlan);
|
|
9190
|
+
}
|
|
9191
|
+
const planMetadataPlan = extractMetadataPlan(item.plan?.metadata);
|
|
9192
|
+
if (planMetadataPlan) {
|
|
9193
|
+
return this.normalizePlanId(planMetadataPlan);
|
|
9194
|
+
}
|
|
9195
|
+
const priceId = item.price?.id;
|
|
9196
|
+
if (priceId) {
|
|
9197
|
+
const mapped = this.pricePlanMap.get(sanitizeKey(priceId));
|
|
9198
|
+
if (mapped) {
|
|
9199
|
+
return mapped;
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
const productId = extractId(item.price?.product ?? item.plan?.product ?? null);
|
|
9203
|
+
if (productId) {
|
|
9204
|
+
const mapped = this.productPlanMap.get(sanitizeKey(productId));
|
|
9205
|
+
if (mapped) {
|
|
9206
|
+
return mapped;
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9209
|
+
const nickname = item.price?.nickname;
|
|
9210
|
+
if (nickname) {
|
|
9211
|
+
const candidate = this.normalizePlanId(nickname);
|
|
9212
|
+
if (candidate !== "free") {
|
|
9213
|
+
return candidate;
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
9216
|
+
}
|
|
9217
|
+
return "free";
|
|
9218
|
+
}
|
|
9219
|
+
getPlanConfig(planId) {
|
|
9220
|
+
return PLAN_CONFIG[planId] ?? PLAN_CONFIG.free;
|
|
9221
|
+
}
|
|
9222
|
+
async applyPlanEntitlements(uid, planId, subscription, overridePeriod) {
|
|
9223
|
+
const config = this.getPlanConfig(planId);
|
|
9224
|
+
await this.store.setEntitlements(uid, planId, config.entitlements);
|
|
9225
|
+
const periodStart = overridePeriod?.start ?? subscription?.current_period_start;
|
|
9226
|
+
overridePeriod?.end ?? subscription?.current_period_end;
|
|
9227
|
+
const periodId = periodStart ? formatPeriodId(new Date(periodStart * 1e3)) : formatPeriodId(Date.now());
|
|
9228
|
+
await this.store.setUsage({
|
|
9229
|
+
uid,
|
|
9230
|
+
planId,
|
|
9231
|
+
periodId,
|
|
9232
|
+
limits: config.quota
|
|
9233
|
+
});
|
|
9234
|
+
}
|
|
9235
|
+
comparePlanPriority(a, b) {
|
|
9236
|
+
return PLAN_PRIORITY[a] - PLAN_PRIORITY[b];
|
|
9237
|
+
}
|
|
9238
|
+
async resolveUid(customerId, ...metadataSources) {
|
|
9239
|
+
for (const metadata of metadataSources) {
|
|
9240
|
+
const uidFromMetadata = extractUidFromMetadata(metadata);
|
|
9241
|
+
if (uidFromMetadata) {
|
|
9242
|
+
return uidFromMetadata;
|
|
9243
|
+
}
|
|
9244
|
+
}
|
|
9245
|
+
if (customerId) {
|
|
9246
|
+
const customer = await this.store.getCustomer(customerId);
|
|
9247
|
+
return customer?.uid;
|
|
9248
|
+
}
|
|
9249
|
+
return void 0;
|
|
9250
|
+
}
|
|
9251
|
+
async handleCheckoutSession(event) {
|
|
9252
|
+
const session = event.data.object;
|
|
9253
|
+
const customerId = extractId(session.customer ?? null);
|
|
9254
|
+
const uid = await this.resolveUid(customerId, session.metadata);
|
|
9255
|
+
if (!customerId) {
|
|
9256
|
+
console.warn("checkout.session.completed received without customer id", { sessionId: session.id });
|
|
9257
|
+
return;
|
|
9258
|
+
}
|
|
9259
|
+
const metadata = {};
|
|
9260
|
+
if (session.metadata) {
|
|
9261
|
+
for (const [key, value] of Object.entries(session.metadata)) {
|
|
9262
|
+
if (typeof value === "string") {
|
|
9263
|
+
metadata[key] = value;
|
|
9264
|
+
}
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
await this.store.upsertCustomer({
|
|
9268
|
+
id: customerId,
|
|
9269
|
+
uid,
|
|
9270
|
+
email: session.customer_email ?? void 0,
|
|
9271
|
+
metadata: {
|
|
9272
|
+
...metadata,
|
|
9273
|
+
lastCheckoutSessionId: session.id,
|
|
9274
|
+
lastCheckoutMode: session.mode ?? void 0
|
|
9275
|
+
}
|
|
9276
|
+
});
|
|
9277
|
+
}
|
|
9278
|
+
async handleSubscriptionCreated(event) {
|
|
9279
|
+
const subscription = event.data.object;
|
|
9280
|
+
const customerId = extractId(subscription.customer);
|
|
9281
|
+
if (!customerId) {
|
|
9282
|
+
throw new Error(`Subscription ${subscription.id} missing customer id`);
|
|
9283
|
+
}
|
|
9284
|
+
const planId = this.resolvePlanFromSubscription(subscription);
|
|
9285
|
+
const uid = await this.resolveUid(customerId, subscription.metadata);
|
|
9286
|
+
const customerRecord = {
|
|
9287
|
+
id: customerId,
|
|
9288
|
+
uid,
|
|
9289
|
+
email: typeof subscription.customer === "object" ? subscription.customer.email ?? void 0 : void 0,
|
|
9290
|
+
metadata: subscription.metadata && Object.keys(subscription.metadata).length > 0 ? { ...subscription.metadata } : void 0,
|
|
9291
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9292
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9293
|
+
};
|
|
9294
|
+
await this.store.upsertCustomer(customerRecord);
|
|
9295
|
+
await this.store.upsertSubscription({
|
|
9296
|
+
id: subscription.id,
|
|
9297
|
+
customerId,
|
|
9298
|
+
uid,
|
|
9299
|
+
status: subscription.status,
|
|
9300
|
+
planId,
|
|
9301
|
+
pendingPlanId: null,
|
|
9302
|
+
pendingPlanEffectiveAt: null,
|
|
9303
|
+
cancelAt: toIso(subscription.cancel_at),
|
|
9304
|
+
cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
|
|
9305
|
+
canceledAt: toIso(subscription.canceled_at),
|
|
9306
|
+
currentPeriodStart: toIso(subscription.current_period_start) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9307
|
+
currentPeriodEnd: toIso(subscription.current_period_end) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9308
|
+
trialEnd: toIso(subscription.trial_end),
|
|
9309
|
+
metadata: subscription.metadata && Object.keys(subscription.metadata).length > 0 ? { ...subscription.metadata } : void 0
|
|
9310
|
+
});
|
|
9311
|
+
if (uid) {
|
|
9312
|
+
await this.applyPlanEntitlements(uid, planId, subscription);
|
|
9313
|
+
}
|
|
9314
|
+
}
|
|
9315
|
+
async handleSubscriptionUpdated(event) {
|
|
9316
|
+
const subscription = event.data.object;
|
|
9317
|
+
const customerId = extractId(subscription.customer);
|
|
9318
|
+
if (!customerId) {
|
|
9319
|
+
throw new Error(`Subscription ${subscription.id} missing customer id`);
|
|
9320
|
+
}
|
|
9321
|
+
const existing = await this.store.getSubscription(subscription.id);
|
|
9322
|
+
const resolvedPlan = this.resolvePlanFromSubscription(subscription);
|
|
9323
|
+
const uid = await this.resolveUid(customerId, subscription.metadata, existing?.metadata);
|
|
9324
|
+
const planChange = existing ? this.comparePlanPriority(resolvedPlan, existing.planId) : 0;
|
|
9325
|
+
let effectivePlan = resolvedPlan;
|
|
9326
|
+
let pendingPlan = existing?.pendingPlanId ?? null;
|
|
9327
|
+
let pendingEffectiveAt = existing?.pendingPlanEffectiveAt ?? null;
|
|
9328
|
+
if (existing) {
|
|
9329
|
+
if (planChange < 0) {
|
|
9330
|
+
effectivePlan = existing.planId;
|
|
9331
|
+
pendingPlan = resolvedPlan;
|
|
9332
|
+
pendingEffectiveAt = toIso(subscription.current_period_end) ?? toIso(subscription.cancel_at) ?? existing.currentPeriodEnd;
|
|
9333
|
+
} else {
|
|
9334
|
+
pendingPlan = null;
|
|
9335
|
+
pendingEffectiveAt = null;
|
|
9336
|
+
if (planChange > 0 && uid) {
|
|
9337
|
+
await this.applyPlanEntitlements(uid, resolvedPlan, subscription);
|
|
9338
|
+
}
|
|
9339
|
+
}
|
|
9340
|
+
} else if (uid) {
|
|
9341
|
+
await this.applyPlanEntitlements(uid, resolvedPlan, subscription);
|
|
9342
|
+
}
|
|
9343
|
+
await this.store.upsertSubscription({
|
|
9344
|
+
id: subscription.id,
|
|
9345
|
+
customerId,
|
|
9346
|
+
uid,
|
|
9347
|
+
status: subscription.status,
|
|
9348
|
+
planId: effectivePlan,
|
|
9349
|
+
pendingPlanId: pendingPlan,
|
|
9350
|
+
pendingPlanEffectiveAt: pendingEffectiveAt,
|
|
9351
|
+
cancelAt: toIso(subscription.cancel_at),
|
|
9352
|
+
cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
|
|
9353
|
+
canceledAt: toIso(subscription.canceled_at),
|
|
9354
|
+
currentPeriodStart: toIso(subscription.current_period_start) ?? existing?.currentPeriodStart ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9355
|
+
currentPeriodEnd: toIso(subscription.current_period_end) ?? existing?.currentPeriodEnd ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9356
|
+
trialEnd: toIso(subscription.trial_end),
|
|
9357
|
+
metadata: subscription.metadata && Object.keys(subscription.metadata).length > 0 ? { ...subscription.metadata } : void 0
|
|
9358
|
+
});
|
|
9359
|
+
if (uid) {
|
|
9360
|
+
await this.store.upsertCustomer({
|
|
9361
|
+
id: customerId,
|
|
9362
|
+
uid,
|
|
9363
|
+
metadata: subscription.metadata && Object.keys(subscription.metadata).length > 0 ? { ...subscription.metadata } : void 0
|
|
9364
|
+
});
|
|
9365
|
+
}
|
|
9366
|
+
}
|
|
9367
|
+
async handleSubscriptionDeleted(event) {
|
|
9368
|
+
const subscription = event.data.object;
|
|
9369
|
+
const customerId = extractId(subscription.customer);
|
|
9370
|
+
const existing = await this.store.getSubscription(subscription.id);
|
|
9371
|
+
const uid = await this.resolveUid(customerId, subscription.metadata, existing?.metadata);
|
|
9372
|
+
await this.store.upsertSubscription({
|
|
9373
|
+
id: subscription.id,
|
|
9374
|
+
customerId: customerId ?? existing?.customerId ?? "unknown",
|
|
9375
|
+
uid: uid ?? existing?.uid,
|
|
9376
|
+
status: "canceled",
|
|
9377
|
+
planId: "free",
|
|
9378
|
+
pendingPlanId: null,
|
|
9379
|
+
pendingPlanEffectiveAt: null,
|
|
9380
|
+
cancelAt: toIso(subscription.cancel_at),
|
|
9381
|
+
cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
|
|
9382
|
+
canceledAt: toIso(subscription.canceled_at) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9383
|
+
currentPeriodStart: toIso(subscription.current_period_start) ?? existing?.currentPeriodStart ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9384
|
+
currentPeriodEnd: toIso(subscription.current_period_end) ?? existing?.currentPeriodEnd ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9385
|
+
trialEnd: toIso(subscription.trial_end),
|
|
9386
|
+
metadata: subscription.metadata && Object.keys(subscription.metadata).length > 0 ? { ...subscription.metadata } : void 0
|
|
9387
|
+
});
|
|
9388
|
+
if (uid) {
|
|
9389
|
+
await this.applyPlanEntitlements(uid, "free", subscription);
|
|
9390
|
+
}
|
|
9391
|
+
}
|
|
9392
|
+
async handleInvoicePayment(event, succeeded) {
|
|
9393
|
+
const invoice = event.data.object;
|
|
9394
|
+
const subscriptionId = extractId(invoice.subscription ?? null);
|
|
9395
|
+
if (!subscriptionId) {
|
|
9396
|
+
console.warn("Invoice received without subscription id", { invoiceId: invoice.id });
|
|
9397
|
+
return;
|
|
9398
|
+
}
|
|
9399
|
+
const subscription = await this.store.getSubscription(subscriptionId);
|
|
9400
|
+
if (!subscription) {
|
|
9401
|
+
console.warn("Invoice received for unknown subscription", { invoiceId: invoice.id, subscriptionId });
|
|
9402
|
+
return;
|
|
9403
|
+
}
|
|
9404
|
+
const customerId = extractId(invoice.customer ?? subscription.customerId);
|
|
9405
|
+
await this.store.recordInvoice({
|
|
9406
|
+
id: invoice.id,
|
|
9407
|
+
subscriptionId,
|
|
9408
|
+
customerId,
|
|
9409
|
+
amountPaid: (invoice.amount_paid ?? 0) / 100,
|
|
9410
|
+
currency: invoice.currency ?? "usd",
|
|
9411
|
+
status: succeeded ? "succeeded" : "failed",
|
|
9412
|
+
paidAt: invoice.created ? new Date(invoice.created * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
9413
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9414
|
+
periodStart: toIso(invoice.lines?.data?.[0]?.period?.start ?? invoice.period_start ?? null),
|
|
9415
|
+
periodEnd: toIso(invoice.lines?.data?.[0]?.period?.end ?? invoice.period_end ?? null),
|
|
9416
|
+
hostedInvoiceUrl: invoice.hosted_invoice_url ?? void 0
|
|
9417
|
+
});
|
|
9418
|
+
if (!succeeded) {
|
|
9419
|
+
await this.store.upsertSubscription({
|
|
9420
|
+
id: subscription.id,
|
|
9421
|
+
customerId: subscription.customerId,
|
|
9422
|
+
uid: subscription.uid,
|
|
9423
|
+
status: subscription.status,
|
|
9424
|
+
planId: subscription.planId,
|
|
9425
|
+
pendingPlanId: subscription.pendingPlanId ?? null,
|
|
9426
|
+
pendingPlanEffectiveAt: subscription.pendingPlanEffectiveAt ?? null,
|
|
9427
|
+
cancelAt: subscription.cancelAt ?? null,
|
|
9428
|
+
cancelAtPeriodEnd: subscription.cancelAtPeriodEnd ?? false,
|
|
9429
|
+
canceledAt: subscription.canceledAt ?? null,
|
|
9430
|
+
currentPeriodStart: subscription.currentPeriodStart,
|
|
9431
|
+
currentPeriodEnd: subscription.currentPeriodEnd,
|
|
9432
|
+
trialEnd: subscription.trialEnd ?? null,
|
|
9433
|
+
latestInvoiceId: invoice.id,
|
|
9434
|
+
latestInvoiceStatus: "failed",
|
|
9435
|
+
latestInvoiceAmount: (invoice.amount_paid ?? 0) / 100
|
|
9436
|
+
});
|
|
9437
|
+
return;
|
|
9438
|
+
}
|
|
9439
|
+
const fallbackPeriodStart = subscription.currentPeriodStart ? Date.parse(subscription.currentPeriodStart) / 1e3 : void 0;
|
|
9440
|
+
const fallbackPeriodEnd = subscription.currentPeriodEnd ? Date.parse(subscription.currentPeriodEnd) / 1e3 : void 0;
|
|
9441
|
+
const periodStart = invoice.lines?.data?.[0]?.period?.start ?? invoice.period_start ?? fallbackPeriodStart;
|
|
9442
|
+
const periodEnd = invoice.lines?.data?.[0]?.period?.end ?? invoice.period_end ?? fallbackPeriodEnd;
|
|
9443
|
+
let nextPlanId = subscription.planId;
|
|
9444
|
+
let pendingPlanId = subscription.pendingPlanId ?? null;
|
|
9445
|
+
let pendingEffectiveAt = subscription.pendingPlanEffectiveAt ?? null;
|
|
9446
|
+
if (subscription.pendingPlanId) {
|
|
9447
|
+
nextPlanId = subscription.pendingPlanId;
|
|
9448
|
+
pendingPlanId = null;
|
|
9449
|
+
pendingEffectiveAt = null;
|
|
9450
|
+
if (subscription.uid) {
|
|
9451
|
+
await this.applyPlanEntitlements(subscription.uid, nextPlanId, null, { start: periodStart, end: periodEnd });
|
|
9452
|
+
}
|
|
9453
|
+
} else if (subscription.uid) {
|
|
9454
|
+
await this.applyPlanEntitlements(subscription.uid, subscription.planId, null, { start: periodStart, end: periodEnd });
|
|
9455
|
+
}
|
|
9456
|
+
await this.store.upsertSubscription({
|
|
9457
|
+
id: subscription.id,
|
|
9458
|
+
customerId: subscription.customerId,
|
|
9459
|
+
uid: subscription.uid,
|
|
9460
|
+
status: subscription.status,
|
|
9461
|
+
planId: nextPlanId,
|
|
9462
|
+
pendingPlanId,
|
|
9463
|
+
pendingPlanEffectiveAt: pendingEffectiveAt,
|
|
9464
|
+
cancelAt: subscription.cancelAt ?? null,
|
|
9465
|
+
cancelAtPeriodEnd: subscription.cancelAtPeriodEnd ?? false,
|
|
9466
|
+
canceledAt: subscription.canceledAt ?? null,
|
|
9467
|
+
currentPeriodStart: periodStart ? toIso(periodStart) ?? subscription.currentPeriodStart : subscription.currentPeriodStart,
|
|
9468
|
+
currentPeriodEnd: periodEnd ? toIso(periodEnd) ?? subscription.currentPeriodEnd : subscription.currentPeriodEnd,
|
|
9469
|
+
trialEnd: subscription.trialEnd ?? null,
|
|
9470
|
+
latestInvoiceId: invoice.id,
|
|
9471
|
+
latestInvoiceStatus: "succeeded",
|
|
9472
|
+
latestInvoiceAmount: (invoice.amount_paid ?? 0) / 100
|
|
9473
|
+
});
|
|
9474
|
+
}
|
|
9475
|
+
};
|
|
9476
|
+
var stripe_webhook_service_default = StripeWebhookService;
|
|
8583
9477
|
|
|
8584
9478
|
// src/server/express-server.ts
|
|
8585
9479
|
var jobIndex = /* @__PURE__ */ new Map();
|
|
8586
9480
|
var app = express__default.default();
|
|
8587
9481
|
var port = process.env.PORT || 8080;
|
|
9482
|
+
var subscriptionStore = new subscription_store_default();
|
|
9483
|
+
var stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
9484
|
+
var stripeWebhookService = null;
|
|
9485
|
+
if (stripeWebhookSecret) {
|
|
9486
|
+
try {
|
|
9487
|
+
stripeWebhookService = new stripe_webhook_service_default({
|
|
9488
|
+
secret: stripeWebhookSecret,
|
|
9489
|
+
store: subscriptionStore
|
|
9490
|
+
});
|
|
9491
|
+
} catch (error) {
|
|
9492
|
+
stripeWebhookService = null;
|
|
9493
|
+
console.error("Failed to initialize Stripe webhook service:", error);
|
|
9494
|
+
}
|
|
9495
|
+
} else {
|
|
9496
|
+
console.warn("STRIPE_WEBHOOK_SECRET is not configured; Stripe webhook endpoint will be disabled.");
|
|
9497
|
+
}
|
|
8588
9498
|
app.use(helmet__default.default());
|
|
8589
9499
|
app.use(cors__default.default());
|
|
8590
9500
|
app.use(compression__default.default({
|
|
@@ -8593,6 +9503,7 @@ app.use(compression__default.default({
|
|
|
8593
9503
|
return compression.filter(req, res);
|
|
8594
9504
|
}
|
|
8595
9505
|
}));
|
|
9506
|
+
app.use("/api/stripe/webhook", express__default.default.raw({ type: "application/json" }));
|
|
8596
9507
|
app.use(express__default.default.json({ limit: "10mb" }));
|
|
8597
9508
|
app.use(express__default.default.urlencoded({ extended: true }));
|
|
8598
9509
|
try {
|
|
@@ -8621,7 +9532,7 @@ app.get("/api/status", (req, res) => {
|
|
|
8621
9532
|
app.get("/", (req, res) => {
|
|
8622
9533
|
res.json({
|
|
8623
9534
|
name: "MARIA CODE API",
|
|
8624
|
-
version: "4.3.
|
|
9535
|
+
version: "4.3.33",
|
|
8625
9536
|
status: "running",
|
|
8626
9537
|
environment: process.env.NODE_ENV || "development",
|
|
8627
9538
|
endpoints: {
|
|
@@ -8636,6 +9547,30 @@ app.get("/", (req, res) => {
|
|
|
8636
9547
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8637
9548
|
});
|
|
8638
9549
|
});
|
|
9550
|
+
app.post("/api/stripe/webhook", async (req, res) => {
|
|
9551
|
+
if (!stripeWebhookService) {
|
|
9552
|
+
return res.status(503).json({ error: "Stripe webhook processing is not configured" });
|
|
9553
|
+
}
|
|
9554
|
+
const signature = req.headers["stripe-signature"];
|
|
9555
|
+
const rawBody = Buffer.isBuffer(req.body) ? req.body : Buffer.from(typeof req.body === "string" ? req.body : JSON.stringify(req.body ?? {}), "utf8");
|
|
9556
|
+
try {
|
|
9557
|
+
stripeWebhookService.verifySignature(rawBody, signature);
|
|
9558
|
+
} catch (error) {
|
|
9559
|
+
console.error("Stripe webhook signature verification failed:", error);
|
|
9560
|
+
return res.status(400).json({ error: "Invalid Stripe signature" });
|
|
9561
|
+
}
|
|
9562
|
+
let event;
|
|
9563
|
+
try {
|
|
9564
|
+
event = stripeWebhookService.parseEvent(rawBody);
|
|
9565
|
+
} catch (error) {
|
|
9566
|
+
console.error("Stripe webhook payload parsing failed:", error);
|
|
9567
|
+
return res.status(400).json({ error: "Invalid Stripe payload" });
|
|
9568
|
+
}
|
|
9569
|
+
stripeWebhookService.processEvent(event).catch((error) => {
|
|
9570
|
+
console.error("Stripe webhook processing error:", error);
|
|
9571
|
+
});
|
|
9572
|
+
return res.sendStatus(200);
|
|
9573
|
+
});
|
|
8639
9574
|
function classifyMediaError(err) {
|
|
8640
9575
|
const raw = err;
|
|
8641
9576
|
const msg = String(raw?.message || raw || "unknown error");
|
|
@@ -9221,7 +10156,7 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
9221
10156
|
} catch {
|
|
9222
10157
|
}
|
|
9223
10158
|
}
|
|
9224
|
-
const
|
|
10159
|
+
const sanitizeKey2 = (v) => {
|
|
9225
10160
|
if (!v) return void 0;
|
|
9226
10161
|
let k = String(v).trim();
|
|
9227
10162
|
if (!k) return void 0;
|
|
@@ -9246,7 +10181,7 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
9246
10181
|
return {};
|
|
9247
10182
|
}
|
|
9248
10183
|
})();
|
|
9249
|
-
const gemKey =
|
|
10184
|
+
const gemKey = sanitizeKey2(keys?.googleApiKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY);
|
|
9250
10185
|
if (gemKey) {
|
|
9251
10186
|
try {
|
|
9252
10187
|
const { GoogleGenerativeAI: GoogleGenerativeAI2 } = await import('@google/generative-ai');
|
|
@@ -9272,7 +10207,7 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
9272
10207
|
}
|
|
9273
10208
|
}
|
|
9274
10209
|
}
|
|
9275
|
-
const openaiKey =
|
|
10210
|
+
const openaiKey = sanitizeKey2(keys?.openaiApiKey || process.env.OPENAI_API_KEY);
|
|
9276
10211
|
if (!openaiKey) {
|
|
9277
10212
|
if (process.env.MARIA_TELEMETRY === "1") {
|
|
9278
10213
|
try {
|