@nextsparkjs/core 0.1.0-beta.127 → 0.1.0-beta.128
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/components/billing/ManageBillingButton.d.ts +3 -3
- package/dist/lib/api/rate-limit.d.ts.map +1 -1
- package/dist/lib/api/rate-limit.js +9 -6
- package/dist/lib/billing/config-types.d.ts +2 -5
- package/dist/lib/billing/config-types.d.ts.map +1 -1
- package/dist/lib/billing/gateways/factory.d.ts +13 -2
- package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
- package/dist/lib/billing/gateways/factory.js +13 -6
- package/dist/lib/billing/gateways/interface.d.ts +19 -1
- package/dist/lib/billing/gateways/interface.d.ts.map +1 -1
- package/dist/lib/billing/gateways/polar.d.ts +8 -1
- package/dist/lib/billing/gateways/polar.d.ts.map +1 -1
- package/dist/lib/billing/gateways/polar.js +25 -0
- package/dist/lib/billing/gateways/stripe.d.ts +8 -26
- package/dist/lib/billing/gateways/stripe.d.ts.map +1 -1
- package/dist/lib/billing/gateways/stripe.js +41 -44
- package/dist/lib/billing/gateways/types.d.ts +11 -0
- package/dist/lib/billing/gateways/types.d.ts.map +1 -1
- package/dist/lib/billing/jobs.d.ts +1 -1
- package/dist/lib/billing/polar-webhook.d.ts +38 -0
- package/dist/lib/billing/polar-webhook.d.ts.map +1 -0
- package/dist/lib/billing/polar-webhook.js +0 -0
- package/dist/lib/billing/schema.d.ts +1 -2
- package/dist/lib/billing/schema.d.ts.map +1 -1
- package/dist/lib/billing/schema.js +1 -1
- package/dist/lib/billing/stripe-webhook.d.ts +48 -0
- package/dist/lib/billing/stripe-webhook.d.ts.map +1 -0
- package/dist/lib/billing/stripe-webhook.js +316 -0
- package/dist/lib/billing/types.d.ts +6 -2
- package/dist/lib/billing/types.d.ts.map +1 -1
- package/dist/lib/rate-limit-redis.d.ts +2 -2
- package/dist/lib/rate-limit-redis.d.ts.map +1 -1
- package/dist/lib/rate-limit-redis.js +22 -4
- package/dist/lib/selectors/core-selectors.d.ts +2 -2
- package/dist/lib/selectors/domains/superadmin.selectors.d.ts +2 -2
- package/dist/lib/selectors/domains/superadmin.selectors.js +2 -2
- package/dist/lib/selectors/selectors.d.ts +4 -4
- package/dist/lib/services/invoice.service.d.ts +3 -3
- package/dist/lib/services/invoice.service.js +2 -2
- package/dist/lib/services/membership.service.d.ts.map +1 -1
- package/dist/lib/services/membership.service.js +29 -0
- package/dist/lib/services/plan.service.d.ts +0 -3
- package/dist/lib/services/plan.service.d.ts.map +1 -1
- package/dist/lib/services/plan.service.js +3 -9
- package/dist/lib/services/subscription.service.d.ts +5 -5
- package/dist/lib/services/subscription.service.d.ts.map +1 -1
- package/dist/lib/services/subscription.service.js +54 -41
- package/dist/migrations/001_better_auth_and_functions.sql +5 -11
- package/dist/migrations/008_team_members_table.sql +27 -23
- package/dist/styles/classes.json +1 -1
- package/dist/templates/app/api/auth/[...all]/route.ts +35 -0
- package/dist/templates/app/api/health/route.ts +43 -23
- package/dist/templates/app/api/internal/user-metadata/route.ts +10 -0
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +5 -0
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -0
- package/dist/templates/app/api/v1/billing/cancel/route.ts +8 -10
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +2 -2
- package/dist/templates/app/api/v1/billing/checkout/route.ts +6 -8
- package/dist/templates/app/api/v1/billing/portal/route.ts +5 -5
- package/dist/templates/app/api/v1/billing/presets.ts +1 -1
- package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +83 -6
- package/dist/templates/app/api/v1/billing/webhooks/stripe/route.ts +18 -421
- package/dist/templates/app/layout.tsx +14 -5
- package/dist/templates/app/superadmin/subscriptions/page.tsx +16 -14
- package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +18 -15
- package/dist/templates/contents/themes/starter/tests/cypress/src/features/SuperadminPOM.ts +2 -2
- package/dist/templates/lib/billing/polar-webhook-extensions.ts +23 -0
- package/dist/templates/lib/billing/stripe-webhook-extensions.ts +23 -0
- package/migrations/001_better_auth_and_functions.sql +5 -11
- package/migrations/008_team_members_table.sql +27 -23
- package/package.json +10 -2
- package/scripts/build/registry/generators/billing-registry.mjs +1 -2
- package/templates/app/api/auth/[...all]/route.ts +35 -0
- package/templates/app/api/health/route.ts +43 -23
- package/templates/app/api/internal/user-metadata/route.ts +10 -0
- package/templates/app/api/superadmin/subscriptions/route.ts +5 -0
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -0
- package/templates/app/api/v1/billing/cancel/route.ts +8 -10
- package/templates/app/api/v1/billing/change-plan/route.ts +2 -2
- package/templates/app/api/v1/billing/checkout/route.ts +6 -8
- package/templates/app/api/v1/billing/portal/route.ts +5 -5
- package/templates/app/api/v1/billing/presets.ts +1 -1
- package/templates/app/api/v1/billing/webhooks/polar/route.ts +83 -6
- package/templates/app/api/v1/billing/webhooks/stripe/route.ts +18 -421
- package/templates/app/layout.tsx +14 -5
- package/templates/app/superadmin/subscriptions/page.tsx +16 -14
- package/templates/app/superadmin/teams/[teamId]/page.tsx +18 -15
- package/templates/contents/themes/starter/tests/cypress/src/features/SuperadminPOM.ts +2 -2
- package/templates/lib/billing/polar-webhook-extensions.ts +23 -0
- package/templates/lib/billing/stripe-webhook-extensions.ts +23 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/billing-registry.ts +7 -8
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Stripe Webhook Handler
|
|
3
|
+
*
|
|
4
|
+
* Provides handleStripeWebhook() — a reusable function that handles the full
|
|
5
|
+
* Stripe subscription lifecycle. Themes/projects call this from their route file
|
|
6
|
+
* and can extend it with onOneTimePaymentCompleted for their custom one-time purchases.
|
|
7
|
+
*
|
|
8
|
+
* Usage in route.ts:
|
|
9
|
+
* import { handleStripeWebhook } from '@nextsparkjs/core/lib/billing/stripe-webhook'
|
|
10
|
+
* export async function POST(request: NextRequest) {
|
|
11
|
+
* return handleStripeWebhook(request)
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* With theme extension:
|
|
15
|
+
* export async function POST(request: NextRequest) {
|
|
16
|
+
* return handleStripeWebhook(request, {
|
|
17
|
+
* onOneTimePaymentCompleted: async (session, { teamId, userId }) => {
|
|
18
|
+
* // theme-specific one-time payment logic
|
|
19
|
+
* }
|
|
20
|
+
* })
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
import { NextRequest } from 'next/server';
|
|
24
|
+
import type { OneTimePaymentContext } from './types';
|
|
25
|
+
export type { OneTimePaymentContext };
|
|
26
|
+
/**
|
|
27
|
+
* Provider-agnostic representation of a Stripe checkout session.
|
|
28
|
+
* Avoids leaking `Stripe.Checkout.Session` SDK type to project code.
|
|
29
|
+
*/
|
|
30
|
+
export interface StripeSessionData {
|
|
31
|
+
id: string;
|
|
32
|
+
amountTotal: number | null;
|
|
33
|
+
currency: string | null;
|
|
34
|
+
customerId: string | null;
|
|
35
|
+
subscriptionId: string | null;
|
|
36
|
+
metadata: Record<string, string>;
|
|
37
|
+
clientReferenceId: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface StripeWebhookExtensions {
|
|
40
|
+
/**
|
|
41
|
+
* Called when a Stripe checkout.session.completed event fires for a session
|
|
42
|
+
* that is NOT a subscription checkout (no planSlug in metadata).
|
|
43
|
+
* Use this for credit packs, one-time purchases, etc.
|
|
44
|
+
*/
|
|
45
|
+
onOneTimePaymentCompleted?: (session: StripeSessionData, context: OneTimePaymentContext) => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export declare function handleStripeWebhook(request: NextRequest, extensions?: StripeWebhookExtensions): Promise<Response>;
|
|
48
|
+
//# sourceMappingURL=stripe-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripe-webhook.d.ts","sourceRoot":"","sources":["../../../src/lib/billing/stripe-webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAKzC,OAAO,KAAK,EAAiB,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAEnE,YAAY,EAAE,qBAAqB,EAAE,CAAA;AAErC;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,CAC1B,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,qBAAqB,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAA;CACnB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,uBAAuB,GACnC,OAAO,CAAC,QAAQ,CAAC,CAsEnB"}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { getBillingGateway } from "./gateways/factory.js";
|
|
2
|
+
import { BILLING_REGISTRY } from "@nextsparkjs/registries/billing-registry";
|
|
3
|
+
import { query, queryOne } from "../db.js";
|
|
4
|
+
async function handleStripeWebhook(request, extensions) {
|
|
5
|
+
const payload = await request.text();
|
|
6
|
+
const signature = request.headers.get("stripe-signature");
|
|
7
|
+
if (!signature) {
|
|
8
|
+
return Response.json({ error: "No signature provided" }, { status: 400 });
|
|
9
|
+
}
|
|
10
|
+
let event;
|
|
11
|
+
try {
|
|
12
|
+
const verified = getBillingGateway().verifyWebhookSignature(payload, signature);
|
|
13
|
+
event = { id: verified.id, type: verified.type, data: verified.data };
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error("[stripe-webhook] Signature verification failed:", error);
|
|
16
|
+
return Response.json({ error: "Invalid signature" }, { status: 400 });
|
|
17
|
+
}
|
|
18
|
+
const eventId = event.id;
|
|
19
|
+
const existing = await queryOne(
|
|
20
|
+
`SELECT id FROM "billing_events" WHERE metadata->>'stripeEventId' = $1`,
|
|
21
|
+
[eventId]
|
|
22
|
+
);
|
|
23
|
+
if (existing) {
|
|
24
|
+
console.log(`[stripe-webhook] Event ${eventId} already processed, skipping`);
|
|
25
|
+
return Response.json({ received: true, status: "duplicate" });
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
console.log(`[stripe-webhook] Processing event type: ${event.type}`);
|
|
29
|
+
switch (event.type) {
|
|
30
|
+
case "checkout.session.completed":
|
|
31
|
+
await handleCheckoutCompleted(event.data.object, extensions);
|
|
32
|
+
break;
|
|
33
|
+
case "invoice.paid":
|
|
34
|
+
await handleInvoicePaid(event.data.object);
|
|
35
|
+
break;
|
|
36
|
+
case "invoice.payment_failed":
|
|
37
|
+
await handlePaymentFailed(event.data.object);
|
|
38
|
+
break;
|
|
39
|
+
case "customer.subscription.updated":
|
|
40
|
+
await handleSubscriptionUpdated(event.data.object);
|
|
41
|
+
break;
|
|
42
|
+
case "customer.subscription.deleted":
|
|
43
|
+
await handleSubscriptionDeleted(event.data.object);
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
console.log(`[stripe-webhook] Unhandled event type: ${event.type}`);
|
|
47
|
+
}
|
|
48
|
+
return Response.json({ received: true });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error("[stripe-webhook] Handler error:", error);
|
|
51
|
+
return Response.json({ error: "Handler failed" }, { status: 500 });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function handleCheckoutCompleted(session, extensions) {
|
|
55
|
+
var _a, _b, _c, _d, _e;
|
|
56
|
+
const teamId = ((_a = session.metadata) == null ? void 0 : _a.teamId) || session.client_reference_id;
|
|
57
|
+
if (!teamId) {
|
|
58
|
+
console.warn("[stripe-webhook] checkout.session.completed has no teamId in metadata \u2014 skipping (likely a test/synthetic event)");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const planSlug = (_b = session.metadata) == null ? void 0 : _b.planSlug;
|
|
62
|
+
const userId = ((_c = session.metadata) == null ? void 0 : _c.userId) || "system";
|
|
63
|
+
if (!planSlug) {
|
|
64
|
+
if (extensions == null ? void 0 : extensions.onOneTimePaymentCompleted) {
|
|
65
|
+
console.log(`[stripe-webhook] One-time payment for team ${teamId}, delegating to theme extension`);
|
|
66
|
+
const sessionData = {
|
|
67
|
+
id: session.id,
|
|
68
|
+
amountTotal: session.amount_total,
|
|
69
|
+
currency: session.currency,
|
|
70
|
+
customerId: typeof session.customer === "string" ? session.customer : ((_d = session.customer) == null ? void 0 : _d.id) ?? null,
|
|
71
|
+
subscriptionId: typeof session.subscription === "string" ? session.subscription : null,
|
|
72
|
+
metadata: session.metadata ?? {},
|
|
73
|
+
clientReferenceId: session.client_reference_id ?? null
|
|
74
|
+
};
|
|
75
|
+
await extensions.onOneTimePaymentCompleted(sessionData, { teamId, userId });
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`[stripe-webhook] One-time payment for team ${teamId} \u2014 no handler registered, skipping`);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const subscriptionId = session.subscription;
|
|
82
|
+
const customerId = session.customer;
|
|
83
|
+
const billingPeriod = ((_e = session.metadata) == null ? void 0 : _e.billingPeriod) || "monthly";
|
|
84
|
+
console.log(`[stripe-webhook] Checkout completed for team ${teamId}, plan: ${planSlug}`);
|
|
85
|
+
let planId = null;
|
|
86
|
+
const planResult = await queryOne(
|
|
87
|
+
`SELECT id FROM plans WHERE slug = $1 LIMIT 1`,
|
|
88
|
+
[planSlug]
|
|
89
|
+
);
|
|
90
|
+
planId = (planResult == null ? void 0 : planResult.id) || null;
|
|
91
|
+
if (!planId) {
|
|
92
|
+
console.warn(`[stripe-webhook] Plan ${planSlug} not found in database, keeping current plan`);
|
|
93
|
+
}
|
|
94
|
+
if (planId) {
|
|
95
|
+
await query(
|
|
96
|
+
`UPDATE subscriptions
|
|
97
|
+
SET "externalSubscriptionId" = $1,
|
|
98
|
+
"externalCustomerId" = $2,
|
|
99
|
+
"paymentProvider" = 'stripe',
|
|
100
|
+
"planId" = $3,
|
|
101
|
+
"billingInterval" = $4,
|
|
102
|
+
status = 'active',
|
|
103
|
+
"updatedAt" = NOW()
|
|
104
|
+
WHERE "teamId" = $5
|
|
105
|
+
AND status IN ('active', 'trialing', 'past_due')`,
|
|
106
|
+
[subscriptionId, customerId, planId, billingPeriod, teamId]
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
await query(
|
|
110
|
+
`UPDATE subscriptions
|
|
111
|
+
SET "externalSubscriptionId" = $1,
|
|
112
|
+
"externalCustomerId" = $2,
|
|
113
|
+
"paymentProvider" = 'stripe',
|
|
114
|
+
status = 'active',
|
|
115
|
+
"updatedAt" = NOW()
|
|
116
|
+
WHERE "teamId" = $3
|
|
117
|
+
AND status IN ('active', 'trialing', 'past_due')`,
|
|
118
|
+
[subscriptionId, customerId, teamId]
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
await logBillingEvent({
|
|
122
|
+
teamId,
|
|
123
|
+
type: "payment",
|
|
124
|
+
status: "succeeded",
|
|
125
|
+
amount: session.amount_total || 0,
|
|
126
|
+
currency: session.currency || "usd",
|
|
127
|
+
stripeEventId: session.id
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function handleInvoicePaid(invoice) {
|
|
131
|
+
var _a, _b, _c;
|
|
132
|
+
const expandedInvoice = invoice;
|
|
133
|
+
const subscriptionId = typeof expandedInvoice.subscription === "string" ? expandedInvoice.subscription : (_a = expandedInvoice.subscription) == null ? void 0 : _a.id;
|
|
134
|
+
if (!subscriptionId) {
|
|
135
|
+
console.log("[stripe-webhook] Invoice has no subscription ID, skipping");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
console.log(`[stripe-webhook] Invoice paid for subscription ${subscriptionId}`);
|
|
139
|
+
if ((_c = (_b = invoice.lines) == null ? void 0 : _b.data) == null ? void 0 : _c[0]) {
|
|
140
|
+
const line = invoice.lines.data[0];
|
|
141
|
+
await query(
|
|
142
|
+
`UPDATE subscriptions
|
|
143
|
+
SET status = 'active',
|
|
144
|
+
"currentPeriodStart" = to_timestamp($1),
|
|
145
|
+
"currentPeriodEnd" = to_timestamp($2),
|
|
146
|
+
"updatedAt" = NOW()
|
|
147
|
+
WHERE "externalSubscriptionId" = $3`,
|
|
148
|
+
[line.period.start, line.period.end, subscriptionId]
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
await query(
|
|
152
|
+
`UPDATE subscriptions
|
|
153
|
+
SET status = 'active',
|
|
154
|
+
"updatedAt" = NOW()
|
|
155
|
+
WHERE "externalSubscriptionId" = $1`,
|
|
156
|
+
[subscriptionId]
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
await syncInvoiceToDatabase(invoice, "paid");
|
|
160
|
+
}
|
|
161
|
+
async function handlePaymentFailed(invoice) {
|
|
162
|
+
var _a;
|
|
163
|
+
const expandedInvoice = invoice;
|
|
164
|
+
const subscriptionId = typeof expandedInvoice.subscription === "string" ? expandedInvoice.subscription : (_a = expandedInvoice.subscription) == null ? void 0 : _a.id;
|
|
165
|
+
if (!subscriptionId) {
|
|
166
|
+
console.log("[stripe-webhook] Invoice has no subscription ID, skipping");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
console.log(`[stripe-webhook] Payment failed for subscription ${subscriptionId}`);
|
|
170
|
+
await query(
|
|
171
|
+
`UPDATE subscriptions
|
|
172
|
+
SET status = 'past_due',
|
|
173
|
+
"updatedAt" = NOW()
|
|
174
|
+
WHERE "externalSubscriptionId" = $1`,
|
|
175
|
+
[subscriptionId]
|
|
176
|
+
);
|
|
177
|
+
await syncInvoiceToDatabase(invoice, "failed");
|
|
178
|
+
}
|
|
179
|
+
async function handleSubscriptionUpdated(subscription) {
|
|
180
|
+
var _a;
|
|
181
|
+
const statusMap = {
|
|
182
|
+
trialing: "trialing",
|
|
183
|
+
active: "active",
|
|
184
|
+
past_due: "past_due",
|
|
185
|
+
canceled: "canceled",
|
|
186
|
+
unpaid: "past_due",
|
|
187
|
+
incomplete: "past_due",
|
|
188
|
+
incomplete_expired: "expired",
|
|
189
|
+
paused: "paused"
|
|
190
|
+
};
|
|
191
|
+
const ourStatus = statusMap[subscription.status] || "active";
|
|
192
|
+
console.log(
|
|
193
|
+
`[stripe-webhook] Subscription updated ${subscription.id}, status: ${subscription.status} -> ${ourStatus}`
|
|
194
|
+
);
|
|
195
|
+
const expandedSubscription = subscription;
|
|
196
|
+
const periodEnd = expandedSubscription.current_period_end ?? null;
|
|
197
|
+
const priceId = (_a = subscription.items.data[0]) == null ? void 0 : _a.price.id;
|
|
198
|
+
let paramIdx = 1;
|
|
199
|
+
const setClauses = [
|
|
200
|
+
`status = $${paramIdx++}`,
|
|
201
|
+
`"cancelAtPeriodEnd" = $${paramIdx++}`
|
|
202
|
+
];
|
|
203
|
+
const params = [
|
|
204
|
+
ourStatus,
|
|
205
|
+
subscription.cancel_at_period_end
|
|
206
|
+
];
|
|
207
|
+
if (periodEnd !== null) {
|
|
208
|
+
setClauses.push(`"currentPeriodEnd" = to_timestamp($${paramIdx++})`);
|
|
209
|
+
params.push(periodEnd);
|
|
210
|
+
}
|
|
211
|
+
setClauses.push(`"updatedAt" = NOW()`);
|
|
212
|
+
if (priceId) {
|
|
213
|
+
const planConfig = BILLING_REGISTRY.plans.find(
|
|
214
|
+
(p) => {
|
|
215
|
+
var _a2, _b;
|
|
216
|
+
return ((_a2 = p.providerPriceIds) == null ? void 0 : _a2.monthly) === priceId || ((_b = p.providerPriceIds) == null ? void 0 : _b.yearly) === priceId;
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
if (planConfig) {
|
|
220
|
+
const planResult = await queryOne(
|
|
221
|
+
`SELECT id FROM plans WHERE slug = $1 LIMIT 1`,
|
|
222
|
+
[planConfig.slug]
|
|
223
|
+
);
|
|
224
|
+
if (planResult) {
|
|
225
|
+
setClauses.push(`"planId" = $${paramIdx++}`);
|
|
226
|
+
params.push(planResult.id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const subIdIdx = paramIdx++;
|
|
231
|
+
params.push(subscription.id);
|
|
232
|
+
await query(
|
|
233
|
+
`UPDATE subscriptions
|
|
234
|
+
SET ${setClauses.join(", ")}
|
|
235
|
+
WHERE "externalSubscriptionId" = $${subIdIdx}`,
|
|
236
|
+
params
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
async function handleSubscriptionDeleted(subscription) {
|
|
240
|
+
console.log(`[stripe-webhook] Subscription deleted ${subscription.id}`);
|
|
241
|
+
await query(
|
|
242
|
+
`UPDATE subscriptions
|
|
243
|
+
SET status = 'canceled',
|
|
244
|
+
"canceledAt" = NOW(),
|
|
245
|
+
"updatedAt" = NOW()
|
|
246
|
+
WHERE "externalSubscriptionId" = $1`,
|
|
247
|
+
[subscription.id]
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
async function logBillingEvent(params) {
|
|
251
|
+
const sub = await queryOne(
|
|
252
|
+
`SELECT id FROM subscriptions WHERE "teamId" = $1 LIMIT 1`,
|
|
253
|
+
[params.teamId]
|
|
254
|
+
);
|
|
255
|
+
if (!sub) {
|
|
256
|
+
console.warn(`[stripe-webhook] No subscription found for team ${params.teamId}, cannot log billing event`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
await query(
|
|
260
|
+
`INSERT INTO "billing_events" ("subscriptionId", type, status, amount, currency, metadata)
|
|
261
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
262
|
+
[
|
|
263
|
+
sub.id,
|
|
264
|
+
params.type,
|
|
265
|
+
params.status,
|
|
266
|
+
params.amount,
|
|
267
|
+
params.currency,
|
|
268
|
+
JSON.stringify({ stripeEventId: params.stripeEventId })
|
|
269
|
+
]
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
async function syncInvoiceToDatabase(invoice, status) {
|
|
273
|
+
var _a;
|
|
274
|
+
const expandedInvoice = invoice;
|
|
275
|
+
const subscriptionId = typeof expandedInvoice.subscription === "string" ? expandedInvoice.subscription : (_a = expandedInvoice.subscription) == null ? void 0 : _a.id;
|
|
276
|
+
if (!subscriptionId) {
|
|
277
|
+
console.warn("[stripe-webhook] Invoice has no subscription, cannot sync to invoices table");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const subResult = await query(
|
|
281
|
+
`SELECT "teamId" FROM subscriptions WHERE "externalSubscriptionId" = $1`,
|
|
282
|
+
[subscriptionId]
|
|
283
|
+
);
|
|
284
|
+
if (!subResult.rows[0]) {
|
|
285
|
+
console.warn(`[stripe-webhook] No subscription found for ${subscriptionId}, cannot sync invoice`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const teamId = subResult.rows[0].teamId;
|
|
289
|
+
const invoiceNumber = invoice.number || invoice.id;
|
|
290
|
+
const amountInDollars = invoice.total / 100;
|
|
291
|
+
await query(
|
|
292
|
+
`INSERT INTO invoices (
|
|
293
|
+
id, "teamId", "invoiceNumber", date, amount, currency, status, "pdfUrl", description
|
|
294
|
+
) VALUES (
|
|
295
|
+
gen_random_uuid()::text, $1, $2, to_timestamp($3), $4, $5, $6::invoice_status, $7, $8
|
|
296
|
+
)
|
|
297
|
+
ON CONFLICT ("teamId", "invoiceNumber") DO UPDATE SET
|
|
298
|
+
status = EXCLUDED.status,
|
|
299
|
+
"pdfUrl" = EXCLUDED."pdfUrl",
|
|
300
|
+
"updatedAt" = NOW()`,
|
|
301
|
+
[
|
|
302
|
+
teamId,
|
|
303
|
+
invoiceNumber,
|
|
304
|
+
invoice.created,
|
|
305
|
+
amountInDollars,
|
|
306
|
+
invoice.currency.toUpperCase(),
|
|
307
|
+
status,
|
|
308
|
+
invoice.invoice_pdf || invoice.hosted_invoice_url || null,
|
|
309
|
+
invoice.description || `Invoice ${invoiceNumber}`
|
|
310
|
+
]
|
|
311
|
+
);
|
|
312
|
+
console.log(`[stripe-webhook] Invoice ${invoiceNumber} synced for team ${teamId} with status ${status}`);
|
|
313
|
+
}
|
|
314
|
+
export {
|
|
315
|
+
handleStripeWebhook
|
|
316
|
+
};
|
|
@@ -25,7 +25,7 @@ export interface Plan {
|
|
|
25
25
|
updatedAt: Date;
|
|
26
26
|
}
|
|
27
27
|
export type SubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'paused' | 'expired';
|
|
28
|
-
export type PaymentProvider = 'stripe' | 'polar'
|
|
28
|
+
export type PaymentProvider = 'stripe' | 'polar';
|
|
29
29
|
export type BillingInterval = 'monthly' | 'yearly';
|
|
30
30
|
export interface Subscription {
|
|
31
31
|
id: string;
|
|
@@ -88,7 +88,7 @@ export interface BillingEvent {
|
|
|
88
88
|
}
|
|
89
89
|
export type InvoiceStatus = 'pending' | 'paid' | 'failed' | 'refunded';
|
|
90
90
|
/**
|
|
91
|
-
* Invoice from
|
|
91
|
+
* Invoice synced from payment provider to local database
|
|
92
92
|
*/
|
|
93
93
|
export interface Invoice {
|
|
94
94
|
id: string;
|
|
@@ -133,6 +133,10 @@ export interface TeamUsageSummary {
|
|
|
133
133
|
byUser: UserUsageSummary[];
|
|
134
134
|
topConsumers: TopConsumer[];
|
|
135
135
|
}
|
|
136
|
+
export interface OneTimePaymentContext {
|
|
137
|
+
teamId: string;
|
|
138
|
+
userId: string;
|
|
139
|
+
}
|
|
136
140
|
export interface CanPerformActionResult {
|
|
137
141
|
allowed: boolean;
|
|
138
142
|
reason?: 'no_permission' | 'feature_not_in_plan' | 'quota_exceeded';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/billing/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,CAAA;AACrD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAA;AAEhE,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,EAAE,QAAQ,CAAA;IACd,UAAU,EAAE,cAAc,CAAA;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAMD,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GACV,QAAQ,GACR,UAAU,GACV,UAAU,GACV,QAAQ,GACR,SAAS,CAAA;AAEb,MAAM,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/billing/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,12 +19,12 @@ export interface RateLimitCheckResult {
|
|
|
19
19
|
/**
|
|
20
20
|
* Rate limit tiers available for API endpoints
|
|
21
21
|
*/
|
|
22
|
-
export type RateLimitTier = 'auth' | 'api' | 'strict' | 'read' | 'write';
|
|
22
|
+
export type RateLimitTier = 'auth' | 'api' | 'strict' | 'read' | 'write' | 'webhook';
|
|
23
23
|
/**
|
|
24
24
|
* Check rate limit for a given identifier
|
|
25
25
|
*
|
|
26
26
|
* @param identifier - Unique identifier (e.g., IP address, user ID, or combination)
|
|
27
|
-
* @param type - Rate limit tier: 'auth' (5/15min), 'api' (100/1min), 'strict' (10/1hr), 'read' (200/1min), 'write' (50/1min)
|
|
27
|
+
* @param type - Rate limit tier: 'auth' (5/15min), 'api' (100/1min), 'strict' (10/1hr), 'read' (200/1min), 'write' (50/1min), 'webhook' (500/1hr)
|
|
28
28
|
* @returns Promise with success status, remaining requests, and reset timestamp
|
|
29
29
|
*/
|
|
30
30
|
export declare function checkRateLimit(identifier: string, type?: RateLimitTier): Promise<RateLimitCheckResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit-redis.d.ts","sourceRoot":"","sources":["../../src/lib/rate-limit-redis.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"rate-limit-redis.d.ts","sourceRoot":"","sources":["../../src/lib/rate-limit-redis.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA2IH,eAAO,MAAM,eAAe,KAAO,CAAA;AACnC,eAAO,MAAM,cAAc,KAAO,CAAA;AAClC,eAAO,MAAM,iBAAiB,KAAO,CAAA;AAErC,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAcpF;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,aAAqB,GAC1B,OAAO,CAAC,oBAAoB,CAAC,CAuD/B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAG1D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAexF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,oBAAoB,GAAG,QAAQ,CAe9E"}
|
|
@@ -2,12 +2,14 @@ let Redis = null;
|
|
|
2
2
|
let Ratelimit = null;
|
|
3
3
|
let modulesLoaded = false;
|
|
4
4
|
let loadError = null;
|
|
5
|
+
let redisUnconfiguredLogged = false;
|
|
5
6
|
let redis = null;
|
|
6
7
|
let authRateLimiterInstance = null;
|
|
7
8
|
let apiRateLimiterInstance = null;
|
|
8
9
|
let strictRateLimiterInstance = null;
|
|
9
10
|
let readRateLimiterInstance = null;
|
|
10
11
|
let writeRateLimiterInstance = null;
|
|
12
|
+
let webhookRateLimiterInstance = null;
|
|
11
13
|
async function loadUpstashModules() {
|
|
12
14
|
if (modulesLoaded) return !loadError;
|
|
13
15
|
try {
|
|
@@ -57,13 +59,20 @@ async function loadUpstashModules() {
|
|
|
57
59
|
prefix: "ratelimit:write",
|
|
58
60
|
ephemeralCache: /* @__PURE__ */ new Map()
|
|
59
61
|
});
|
|
62
|
+
webhookRateLimiterInstance = new Ratelimit({
|
|
63
|
+
redis,
|
|
64
|
+
limiter: Ratelimit.slidingWindow(500, "1 h"),
|
|
65
|
+
analytics: true,
|
|
66
|
+
prefix: "ratelimit:webhook",
|
|
67
|
+
ephemeralCache: /* @__PURE__ */ new Map()
|
|
68
|
+
});
|
|
60
69
|
}
|
|
61
70
|
modulesLoaded = true;
|
|
62
71
|
return true;
|
|
63
72
|
} catch (error) {
|
|
64
73
|
loadError = error;
|
|
65
74
|
modulesLoaded = true;
|
|
66
|
-
if (process.env.NODE_ENV
|
|
75
|
+
if (process.env.NODE_ENV !== "production") {
|
|
67
76
|
console.warn("[RateLimit] Upstash modules not available, using fallback:", error.message);
|
|
68
77
|
}
|
|
69
78
|
return false;
|
|
@@ -77,7 +86,8 @@ const DEFAULT_TIER_LIMITS = {
|
|
|
77
86
|
api: 100,
|
|
78
87
|
strict: 10,
|
|
79
88
|
read: 200,
|
|
80
|
-
write: 50
|
|
89
|
+
write: 50,
|
|
90
|
+
webhook: 500
|
|
81
91
|
};
|
|
82
92
|
async function checkRateLimit(identifier, type = "api") {
|
|
83
93
|
await loadUpstashModules();
|
|
@@ -86,11 +96,19 @@ async function checkRateLimit(identifier, type = "api") {
|
|
|
86
96
|
api: apiRateLimiterInstance,
|
|
87
97
|
strict: strictRateLimiterInstance,
|
|
88
98
|
read: readRateLimiterInstance,
|
|
89
|
-
write: writeRateLimiterInstance
|
|
99
|
+
write: writeRateLimiterInstance,
|
|
100
|
+
webhook: webhookRateLimiterInstance
|
|
90
101
|
};
|
|
91
102
|
const limiter = limiterMap[type];
|
|
92
103
|
if (!limiter) {
|
|
93
|
-
|
|
104
|
+
if (!redisUnconfiguredLogged) {
|
|
105
|
+
redisUnconfiguredLogged = true;
|
|
106
|
+
console.error(
|
|
107
|
+
"[RateLimit] CRITICAL: Rate limiting is DISABLED (Redis unavailable).",
|
|
108
|
+
"Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN to enable distributed rate limiting.",
|
|
109
|
+
"All requests are currently allowed through without limit enforcement."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
94
112
|
return {
|
|
95
113
|
success: true,
|
|
96
114
|
remaining: DEFAULT_TIER_LIMITS[type],
|
|
@@ -1091,7 +1091,7 @@ export declare const CORE_SELECTORS: {
|
|
|
1091
1091
|
readonly plan: "superadmin-team-plan";
|
|
1092
1092
|
readonly status: "superadmin-team-sub-status";
|
|
1093
1093
|
readonly period: "superadmin-team-sub-period";
|
|
1094
|
-
readonly
|
|
1094
|
+
readonly providerLink: "superadmin-team-provider-link";
|
|
1095
1095
|
};
|
|
1096
1096
|
readonly billingHistory: {
|
|
1097
1097
|
readonly container: "superadmin-team-billing";
|
|
@@ -1130,7 +1130,7 @@ export declare const CORE_SELECTORS: {
|
|
|
1130
1130
|
readonly element: "superadmin-subscriptions-table";
|
|
1131
1131
|
readonly row: "superadmin-subscriptions-row-{id}";
|
|
1132
1132
|
readonly viewTeamButton: "superadmin-subscriptions-view-team-{id}";
|
|
1133
|
-
readonly
|
|
1133
|
+
readonly providerLink: "superadmin-subscriptions-provider-{id}";
|
|
1134
1134
|
};
|
|
1135
1135
|
readonly pagination: {
|
|
1136
1136
|
readonly container: "superadmin-subscriptions-pagination";
|
|
@@ -211,7 +211,7 @@ export declare const SUPERADMIN_SELECTORS: {
|
|
|
211
211
|
readonly plan: "superadmin-team-plan";
|
|
212
212
|
readonly status: "superadmin-team-sub-status";
|
|
213
213
|
readonly period: "superadmin-team-sub-period";
|
|
214
|
-
readonly
|
|
214
|
+
readonly providerLink: "superadmin-team-provider-link";
|
|
215
215
|
};
|
|
216
216
|
readonly billingHistory: {
|
|
217
217
|
readonly container: "superadmin-team-billing";
|
|
@@ -250,7 +250,7 @@ export declare const SUPERADMIN_SELECTORS: {
|
|
|
250
250
|
readonly element: "superadmin-subscriptions-table";
|
|
251
251
|
readonly row: "superadmin-subscriptions-row-{id}";
|
|
252
252
|
readonly viewTeamButton: "superadmin-subscriptions-view-team-{id}";
|
|
253
|
-
readonly
|
|
253
|
+
readonly providerLink: "superadmin-subscriptions-provider-{id}";
|
|
254
254
|
};
|
|
255
255
|
readonly pagination: {
|
|
256
256
|
readonly container: "superadmin-subscriptions-pagination";
|
|
@@ -168,7 +168,7 @@ const SUPERADMIN_SELECTORS = {
|
|
|
168
168
|
plan: "superadmin-team-plan",
|
|
169
169
|
status: "superadmin-team-sub-status",
|
|
170
170
|
period: "superadmin-team-sub-period",
|
|
171
|
-
|
|
171
|
+
providerLink: "superadmin-team-provider-link"
|
|
172
172
|
},
|
|
173
173
|
billingHistory: {
|
|
174
174
|
container: "superadmin-team-billing",
|
|
@@ -210,7 +210,7 @@ const SUPERADMIN_SELECTORS = {
|
|
|
210
210
|
element: "superadmin-subscriptions-table",
|
|
211
211
|
row: "superadmin-subscriptions-row-{id}",
|
|
212
212
|
viewTeamButton: "superadmin-subscriptions-view-team-{id}",
|
|
213
|
-
|
|
213
|
+
providerLink: "superadmin-subscriptions-provider-{id}"
|
|
214
214
|
},
|
|
215
215
|
pagination: {
|
|
216
216
|
container: "superadmin-subscriptions-pagination",
|
|
@@ -1090,7 +1090,7 @@ export declare const SELECTORS: {
|
|
|
1090
1090
|
readonly plan: "superadmin-team-plan";
|
|
1091
1091
|
readonly status: "superadmin-team-sub-status";
|
|
1092
1092
|
readonly period: "superadmin-team-sub-period";
|
|
1093
|
-
readonly
|
|
1093
|
+
readonly providerLink: "superadmin-team-provider-link";
|
|
1094
1094
|
};
|
|
1095
1095
|
readonly billingHistory: {
|
|
1096
1096
|
readonly container: "superadmin-team-billing";
|
|
@@ -1129,7 +1129,7 @@ export declare const SELECTORS: {
|
|
|
1129
1129
|
readonly element: "superadmin-subscriptions-table";
|
|
1130
1130
|
readonly row: "superadmin-subscriptions-row-{id}";
|
|
1131
1131
|
readonly viewTeamButton: "superadmin-subscriptions-view-team-{id}";
|
|
1132
|
-
readonly
|
|
1132
|
+
readonly providerLink: "superadmin-subscriptions-provider-{id}";
|
|
1133
1133
|
};
|
|
1134
1134
|
readonly pagination: {
|
|
1135
1135
|
readonly container: "superadmin-subscriptions-pagination";
|
|
@@ -2671,7 +2671,7 @@ declare const _default: {
|
|
|
2671
2671
|
readonly plan: "superadmin-team-plan";
|
|
2672
2672
|
readonly status: "superadmin-team-sub-status";
|
|
2673
2673
|
readonly period: "superadmin-team-sub-period";
|
|
2674
|
-
readonly
|
|
2674
|
+
readonly providerLink: "superadmin-team-provider-link";
|
|
2675
2675
|
};
|
|
2676
2676
|
readonly billingHistory: {
|
|
2677
2677
|
readonly container: "superadmin-team-billing";
|
|
@@ -2710,7 +2710,7 @@ declare const _default: {
|
|
|
2710
2710
|
readonly element: "superadmin-subscriptions-table";
|
|
2711
2711
|
readonly row: "superadmin-subscriptions-row-{id}";
|
|
2712
2712
|
readonly viewTeamButton: "superadmin-subscriptions-view-team-{id}";
|
|
2713
|
-
readonly
|
|
2713
|
+
readonly providerLink: "superadmin-subscriptions-provider-{id}";
|
|
2714
2714
|
};
|
|
2715
2715
|
readonly pagination: {
|
|
2716
2716
|
readonly container: "superadmin-subscriptions-pagination";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Invoice Service
|
|
3
3
|
*
|
|
4
4
|
* Provides invoice management functions for billing operations.
|
|
5
|
-
* Invoices are synced from
|
|
5
|
+
* Invoices are synced from the payment provider and stored locally for fast access.
|
|
6
6
|
*
|
|
7
7
|
* @module InvoiceService
|
|
8
8
|
*/
|
|
@@ -180,11 +180,11 @@ export declare class InvoiceService {
|
|
|
180
180
|
* Update invoice PDF URL
|
|
181
181
|
*
|
|
182
182
|
* @param id - Invoice ID
|
|
183
|
-
* @param pdfUrl - PDF URL from
|
|
183
|
+
* @param pdfUrl - PDF URL from payment provider
|
|
184
184
|
* @returns Updated invoice
|
|
185
185
|
*
|
|
186
186
|
* @example
|
|
187
|
-
* await InvoiceService.updatePdfUrl('inv-123', 'https://
|
|
187
|
+
* await InvoiceService.updatePdfUrl('inv-123', 'https://example.com/invoice.pdf')
|
|
188
188
|
*/
|
|
189
189
|
static updatePdfUrl(id: string, pdfUrl: string): Promise<Invoice>;
|
|
190
190
|
/**
|
|
@@ -348,11 +348,11 @@ class InvoiceService {
|
|
|
348
348
|
* Update invoice PDF URL
|
|
349
349
|
*
|
|
350
350
|
* @param id - Invoice ID
|
|
351
|
-
* @param pdfUrl - PDF URL from
|
|
351
|
+
* @param pdfUrl - PDF URL from payment provider
|
|
352
352
|
* @returns Updated invoice
|
|
353
353
|
*
|
|
354
354
|
* @example
|
|
355
|
-
* await InvoiceService.updatePdfUrl('inv-123', 'https://
|
|
355
|
+
* await InvoiceService.updatePdfUrl('inv-123', 'https://example.com/invoice.pdf')
|
|
356
356
|
*/
|
|
357
357
|
static async updatePdfUrl(id, pdfUrl) {
|
|
358
358
|
if (!id || id.trim() === "") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/membership.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"membership.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/membership.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,kBAAkB,EACnB,MAAM,sBAAsB,CAAA;AAM7B;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,kBAAkB;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,UAAU,EAAE,CAAA;IAClC,QAAQ,CAAC,YAAY,EAAE,sBAAsB,GAAG,IAAI,CAAA;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;gBAE/B,IAAI,EAAE,kBAAkB;IAWpC;;;;;;;;;;OAUG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIvC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI9B;;;;;;;;;;OAUG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAKpC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO;IAI9C;;;;;;;;;;OAUG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;;;;;;;;;OAYG;IACH,UAAU,CACR,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAAU,GACpB;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAmB1C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GACpC,YAAY;CA0EhB;AAMD,qBAAa,iBAAiB;IAC5B;;;;;;;;;;;;;;;;;;OAkBG;WACU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAkEzE;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAiBlC;;;;;;;;OAQG;mBACkB,cAAc;CAyCpC"}
|