@codaijs/keel 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/sail-installer.test.js +25 -25
- package/dist/sail-installer.js +174 -174
- package/dist/scaffold.js +68 -68
- package/package.json +58 -58
- package/sails/_template/addon.json +20 -20
- package/sails/_template/install.ts +402 -402
- package/sails/admin-dashboard/README.md +117 -117
- package/sails/admin-dashboard/addon.json +28 -28
- package/sails/admin-dashboard/files/backend/middleware/admin.ts +34 -34
- package/sails/admin-dashboard/files/backend/routes/admin.ts +243 -243
- package/sails/admin-dashboard/files/frontend/components/admin/StatsCard.tsx +40 -40
- package/sails/admin-dashboard/files/frontend/components/admin/UsersTable.tsx +240 -240
- package/sails/admin-dashboard/files/frontend/hooks/useAdmin.ts +149 -149
- package/sails/admin-dashboard/files/frontend/pages/admin/Dashboard.tsx +173 -173
- package/sails/admin-dashboard/files/frontend/pages/admin/UserDetail.tsx +203 -203
- package/sails/admin-dashboard/install.ts +305 -305
- package/sails/analytics/README.md +178 -178
- package/sails/analytics/addon.json +27 -27
- package/sails/analytics/files/frontend/components/AnalyticsProvider.tsx +58 -58
- package/sails/analytics/files/frontend/hooks/useAnalytics.ts +64 -64
- package/sails/analytics/files/frontend/lib/analytics.ts +103 -103
- package/sails/analytics/install.ts +297 -297
- package/sails/file-uploads/addon.json +30 -30
- package/sails/file-uploads/files/backend/routes/files.ts +198 -198
- package/sails/file-uploads/files/backend/schema/files.ts +36 -36
- package/sails/file-uploads/files/backend/services/file-storage.ts +128 -128
- package/sails/file-uploads/files/frontend/components/FileList.tsx +248 -248
- package/sails/file-uploads/files/frontend/components/FileUploadButton.tsx +147 -147
- package/sails/file-uploads/files/frontend/hooks/useFileUpload.ts +106 -106
- package/sails/file-uploads/files/frontend/hooks/useFiles.ts +118 -118
- package/sails/file-uploads/files/frontend/pages/Files.tsx +37 -37
- package/sails/file-uploads/install.ts +466 -466
- package/sails/gdpr/README.md +174 -174
- package/sails/gdpr/addon.json +27 -27
- package/sails/gdpr/files/backend/routes/gdpr.ts +140 -140
- package/sails/gdpr/files/backend/services/gdpr.ts +293 -293
- package/sails/gdpr/files/frontend/components/auth/ConsentCheckboxes.tsx +97 -97
- package/sails/gdpr/files/frontend/components/gdpr/AccountDeletionRequest.tsx +192 -192
- package/sails/gdpr/files/frontend/components/gdpr/DataExportButton.tsx +75 -75
- package/sails/gdpr/files/frontend/pages/PrivacyPolicy.tsx +186 -186
- package/sails/gdpr/install.ts +756 -756
- package/sails/google-oauth/README.md +121 -121
- package/sails/google-oauth/addon.json +22 -22
- package/sails/google-oauth/files/GoogleButton.tsx +50 -50
- package/sails/google-oauth/install.ts +252 -252
- package/sails/i18n/README.md +193 -193
- package/sails/i18n/addon.json +30 -30
- package/sails/i18n/files/frontend/components/LanguageSwitcher.tsx +108 -108
- package/sails/i18n/files/frontend/hooks/useLanguage.ts +31 -31
- package/sails/i18n/files/frontend/lib/i18n.ts +32 -32
- package/sails/i18n/files/frontend/locales/de/common.json +44 -44
- package/sails/i18n/files/frontend/locales/en/common.json +44 -44
- package/sails/i18n/install.ts +407 -407
- package/sails/push-notifications/README.md +163 -163
- package/sails/push-notifications/addon.json +31 -31
- package/sails/push-notifications/files/backend/routes/notifications.ts +153 -153
- package/sails/push-notifications/files/backend/schema/notifications.ts +31 -31
- package/sails/push-notifications/files/backend/services/notifications.ts +117 -117
- package/sails/push-notifications/files/frontend/components/PushNotificationInit.tsx +12 -12
- package/sails/push-notifications/files/frontend/hooks/usePushNotifications.ts +154 -154
- package/sails/push-notifications/install.ts +384 -384
- package/sails/r2-storage/addon.json +29 -29
- package/sails/r2-storage/files/backend/services/storage.ts +71 -71
- package/sails/r2-storage/files/frontend/components/ProfilePictureUpload.tsx +167 -167
- package/sails/r2-storage/install.ts +412 -412
- package/sails/rate-limiting/addon.json +20 -20
- package/sails/rate-limiting/files/backend/middleware/rate-limit-store.ts +104 -104
- package/sails/rate-limiting/files/backend/middleware/rate-limit.ts +137 -137
- package/sails/rate-limiting/install.ts +300 -300
- package/sails/registry.json +107 -107
- package/sails/stripe/README.md +214 -214
- package/sails/stripe/addon.json +24 -24
- package/sails/stripe/files/backend/routes/stripe.ts +154 -154
- package/sails/stripe/files/backend/schema/stripe.ts +74 -74
- package/sails/stripe/files/backend/services/stripe.ts +224 -224
- package/sails/stripe/files/frontend/components/SubscriptionStatus.tsx +135 -135
- package/sails/stripe/files/frontend/hooks/useSubscription.ts +86 -86
- package/sails/stripe/files/frontend/pages/Checkout.tsx +116 -116
- package/sails/stripe/files/frontend/pages/Pricing.tsx +226 -226
- package/sails/stripe/install.ts +378 -378
|
@@ -1,224 +1,224 @@
|
|
|
1
|
-
import Stripe from "stripe";
|
|
2
|
-
import { eq } from "drizzle-orm";
|
|
3
|
-
import { db } from "../db";
|
|
4
|
-
import { customers, subscriptions } from "../db/schema/stripe";
|
|
5
|
-
import { env } from "../env";
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Stripe SDK instance
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
export const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
|
12
|
-
apiVersion: "2024-12-18.acacia",
|
|
13
|
-
typescript: true,
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Customer management
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Retrieve the existing Stripe customer for a user, or create one.
|
|
22
|
-
*/
|
|
23
|
-
export async function createOrGetCustomer(
|
|
24
|
-
userId: string,
|
|
25
|
-
email: string
|
|
26
|
-
): Promise<string> {
|
|
27
|
-
// Check for existing customer record
|
|
28
|
-
const existing = await db.query.customers.findFirst({
|
|
29
|
-
where: eq(customers.userId, userId),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (existing) {
|
|
33
|
-
return existing.stripeCustomerId;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Create a new Stripe customer
|
|
37
|
-
const stripeCustomer = await stripe.customers.create({
|
|
38
|
-
email,
|
|
39
|
-
metadata: { userId },
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Persist the mapping
|
|
43
|
-
await db.insert(customers).values({
|
|
44
|
-
userId,
|
|
45
|
-
stripeCustomerId: stripeCustomer.id,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return stripeCustomer.id;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Checkout session
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Create a Stripe Checkout session for a subscription.
|
|
57
|
-
*/
|
|
58
|
-
export async function createCheckoutSession(
|
|
59
|
-
customerId: string,
|
|
60
|
-
priceId: string,
|
|
61
|
-
successUrl: string,
|
|
62
|
-
cancelUrl: string
|
|
63
|
-
): Promise<Stripe.Checkout.Session> {
|
|
64
|
-
return stripe.checkout.sessions.create({
|
|
65
|
-
customer: customerId,
|
|
66
|
-
mode: "subscription",
|
|
67
|
-
payment_method_types: ["card"],
|
|
68
|
-
line_items: [{ price: priceId, quantity: 1 }],
|
|
69
|
-
success_url: successUrl,
|
|
70
|
-
cancel_url: cancelUrl,
|
|
71
|
-
subscription_data: {
|
|
72
|
-
metadata: { customerId },
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// Customer portal session
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create a Stripe Customer Portal session so the user can manage their
|
|
83
|
-
* subscription, update payment methods, or cancel.
|
|
84
|
-
*/
|
|
85
|
-
export async function createPortalSession(
|
|
86
|
-
customerId: string,
|
|
87
|
-
returnUrl: string
|
|
88
|
-
): Promise<Stripe.BillingPortal.Session> {
|
|
89
|
-
return stripe.billingPortal.sessions.create({
|
|
90
|
-
customer: customerId,
|
|
91
|
-
return_url: returnUrl,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// Webhook event handler
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Verify and process an incoming Stripe webhook event.
|
|
101
|
-
*/
|
|
102
|
-
export async function handleWebhookEvent(
|
|
103
|
-
payload: string | Buffer,
|
|
104
|
-
signature: string
|
|
105
|
-
): Promise<void> {
|
|
106
|
-
const event = stripe.webhooks.constructEvent(
|
|
107
|
-
payload,
|
|
108
|
-
signature,
|
|
109
|
-
env.STRIPE_WEBHOOK_SECRET
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
switch (event.type) {
|
|
113
|
-
case "checkout.session.completed": {
|
|
114
|
-
const session = event.data.object as Stripe.Checkout.Session;
|
|
115
|
-
await handleCheckoutCompleted(session);
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
case "customer.subscription.updated": {
|
|
120
|
-
const subscription = event.data.object as Stripe.Subscription;
|
|
121
|
-
await handleSubscriptionUpdated(subscription);
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
case "customer.subscription.deleted": {
|
|
126
|
-
const subscription = event.data.object as Stripe.Subscription;
|
|
127
|
-
await handleSubscriptionDeleted(subscription);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
default:
|
|
132
|
-
console.log(`Unhandled Stripe event type: ${event.type}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
// Internal event handlers
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
|
|
140
|
-
async function handleCheckoutCompleted(
|
|
141
|
-
session: Stripe.Checkout.Session
|
|
142
|
-
): Promise<void> {
|
|
143
|
-
if (session.mode !== "subscription" || !session.subscription) return;
|
|
144
|
-
|
|
145
|
-
const stripeSubscriptionId =
|
|
146
|
-
typeof session.subscription === "string"
|
|
147
|
-
? session.subscription
|
|
148
|
-
: session.subscription.id;
|
|
149
|
-
|
|
150
|
-
const stripeCustomerId =
|
|
151
|
-
typeof session.customer === "string"
|
|
152
|
-
? session.customer
|
|
153
|
-
: session.customer?.id;
|
|
154
|
-
|
|
155
|
-
if (!stripeCustomerId) return;
|
|
156
|
-
|
|
157
|
-
// Look up the internal customer record
|
|
158
|
-
const customer = await db.query.customers.findFirst({
|
|
159
|
-
where: eq(customers.stripeCustomerId, stripeCustomerId),
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (!customer) {
|
|
163
|
-
console.error(
|
|
164
|
-
`No customer record found for Stripe customer ${stripeCustomerId}`
|
|
165
|
-
);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Fetch the full subscription from Stripe to get price info
|
|
170
|
-
const stripeSub = await stripe.subscriptions.retrieve(stripeSubscriptionId);
|
|
171
|
-
const priceId = stripeSub.items.data[0]?.price.id ?? "";
|
|
172
|
-
|
|
173
|
-
// Upsert the subscription record
|
|
174
|
-
await db
|
|
175
|
-
.insert(subscriptions)
|
|
176
|
-
.values({
|
|
177
|
-
customerId: customer.id,
|
|
178
|
-
stripeSubscriptionId,
|
|
179
|
-
status: stripeSub.status,
|
|
180
|
-
stripePriceId: priceId,
|
|
181
|
-
currentPeriodStart: new Date(stripeSub.current_period_start * 1000),
|
|
182
|
-
currentPeriodEnd: new Date(stripeSub.current_period_end * 1000),
|
|
183
|
-
cancelAtPeriodEnd: stripeSub.cancel_at_period_end,
|
|
184
|
-
})
|
|
185
|
-
.onConflictDoUpdate({
|
|
186
|
-
target: subscriptions.stripeSubscriptionId,
|
|
187
|
-
set: {
|
|
188
|
-
status: stripeSub.status,
|
|
189
|
-
stripePriceId: priceId,
|
|
190
|
-
currentPeriodStart: new Date(stripeSub.current_period_start * 1000),
|
|
191
|
-
currentPeriodEnd: new Date(stripeSub.current_period_end * 1000),
|
|
192
|
-
cancelAtPeriodEnd: stripeSub.cancel_at_period_end,
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async function handleSubscriptionUpdated(
|
|
198
|
-
subscription: Stripe.Subscription
|
|
199
|
-
): Promise<void> {
|
|
200
|
-
const priceId = subscription.items.data[0]?.price.id ?? "";
|
|
201
|
-
|
|
202
|
-
await db
|
|
203
|
-
.update(subscriptions)
|
|
204
|
-
.set({
|
|
205
|
-
status: subscription.status,
|
|
206
|
-
stripePriceId: priceId,
|
|
207
|
-
currentPeriodStart: new Date(subscription.current_period_start * 1000),
|
|
208
|
-
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
|
|
209
|
-
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
|
210
|
-
})
|
|
211
|
-
.where(eq(subscriptions.stripeSubscriptionId, subscription.id));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async function handleSubscriptionDeleted(
|
|
215
|
-
subscription: Stripe.Subscription
|
|
216
|
-
): Promise<void> {
|
|
217
|
-
await db
|
|
218
|
-
.update(subscriptions)
|
|
219
|
-
.set({
|
|
220
|
-
status: "canceled",
|
|
221
|
-
cancelAtPeriodEnd: false,
|
|
222
|
-
})
|
|
223
|
-
.where(eq(subscriptions.stripeSubscriptionId, subscription.id));
|
|
224
|
-
}
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { db } from "../db";
|
|
4
|
+
import { customers, subscriptions } from "../db/schema/stripe";
|
|
5
|
+
import { env } from "../env";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Stripe SDK instance
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
|
12
|
+
apiVersion: "2024-12-18.acacia",
|
|
13
|
+
typescript: true,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Customer management
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Retrieve the existing Stripe customer for a user, or create one.
|
|
22
|
+
*/
|
|
23
|
+
export async function createOrGetCustomer(
|
|
24
|
+
userId: string,
|
|
25
|
+
email: string
|
|
26
|
+
): Promise<string> {
|
|
27
|
+
// Check for existing customer record
|
|
28
|
+
const existing = await db.query.customers.findFirst({
|
|
29
|
+
where: eq(customers.userId, userId),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (existing) {
|
|
33
|
+
return existing.stripeCustomerId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Create a new Stripe customer
|
|
37
|
+
const stripeCustomer = await stripe.customers.create({
|
|
38
|
+
email,
|
|
39
|
+
metadata: { userId },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Persist the mapping
|
|
43
|
+
await db.insert(customers).values({
|
|
44
|
+
userId,
|
|
45
|
+
stripeCustomerId: stripeCustomer.id,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return stripeCustomer.id;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Checkout session
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a Stripe Checkout session for a subscription.
|
|
57
|
+
*/
|
|
58
|
+
export async function createCheckoutSession(
|
|
59
|
+
customerId: string,
|
|
60
|
+
priceId: string,
|
|
61
|
+
successUrl: string,
|
|
62
|
+
cancelUrl: string
|
|
63
|
+
): Promise<Stripe.Checkout.Session> {
|
|
64
|
+
return stripe.checkout.sessions.create({
|
|
65
|
+
customer: customerId,
|
|
66
|
+
mode: "subscription",
|
|
67
|
+
payment_method_types: ["card"],
|
|
68
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
69
|
+
success_url: successUrl,
|
|
70
|
+
cancel_url: cancelUrl,
|
|
71
|
+
subscription_data: {
|
|
72
|
+
metadata: { customerId },
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Customer portal session
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a Stripe Customer Portal session so the user can manage their
|
|
83
|
+
* subscription, update payment methods, or cancel.
|
|
84
|
+
*/
|
|
85
|
+
export async function createPortalSession(
|
|
86
|
+
customerId: string,
|
|
87
|
+
returnUrl: string
|
|
88
|
+
): Promise<Stripe.BillingPortal.Session> {
|
|
89
|
+
return stripe.billingPortal.sessions.create({
|
|
90
|
+
customer: customerId,
|
|
91
|
+
return_url: returnUrl,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Webhook event handler
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Verify and process an incoming Stripe webhook event.
|
|
101
|
+
*/
|
|
102
|
+
export async function handleWebhookEvent(
|
|
103
|
+
payload: string | Buffer,
|
|
104
|
+
signature: string
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
const event = stripe.webhooks.constructEvent(
|
|
107
|
+
payload,
|
|
108
|
+
signature,
|
|
109
|
+
env.STRIPE_WEBHOOK_SECRET
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
switch (event.type) {
|
|
113
|
+
case "checkout.session.completed": {
|
|
114
|
+
const session = event.data.object as Stripe.Checkout.Session;
|
|
115
|
+
await handleCheckoutCompleted(session);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case "customer.subscription.updated": {
|
|
120
|
+
const subscription = event.data.object as Stripe.Subscription;
|
|
121
|
+
await handleSubscriptionUpdated(subscription);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case "customer.subscription.deleted": {
|
|
126
|
+
const subscription = event.data.object as Stripe.Subscription;
|
|
127
|
+
await handleSubscriptionDeleted(subscription);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
default:
|
|
132
|
+
console.log(`Unhandled Stripe event type: ${event.type}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Internal event handlers
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
async function handleCheckoutCompleted(
|
|
141
|
+
session: Stripe.Checkout.Session
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
if (session.mode !== "subscription" || !session.subscription) return;
|
|
144
|
+
|
|
145
|
+
const stripeSubscriptionId =
|
|
146
|
+
typeof session.subscription === "string"
|
|
147
|
+
? session.subscription
|
|
148
|
+
: session.subscription.id;
|
|
149
|
+
|
|
150
|
+
const stripeCustomerId =
|
|
151
|
+
typeof session.customer === "string"
|
|
152
|
+
? session.customer
|
|
153
|
+
: session.customer?.id;
|
|
154
|
+
|
|
155
|
+
if (!stripeCustomerId) return;
|
|
156
|
+
|
|
157
|
+
// Look up the internal customer record
|
|
158
|
+
const customer = await db.query.customers.findFirst({
|
|
159
|
+
where: eq(customers.stripeCustomerId, stripeCustomerId),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!customer) {
|
|
163
|
+
console.error(
|
|
164
|
+
`No customer record found for Stripe customer ${stripeCustomerId}`
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Fetch the full subscription from Stripe to get price info
|
|
170
|
+
const stripeSub = await stripe.subscriptions.retrieve(stripeSubscriptionId);
|
|
171
|
+
const priceId = stripeSub.items.data[0]?.price.id ?? "";
|
|
172
|
+
|
|
173
|
+
// Upsert the subscription record
|
|
174
|
+
await db
|
|
175
|
+
.insert(subscriptions)
|
|
176
|
+
.values({
|
|
177
|
+
customerId: customer.id,
|
|
178
|
+
stripeSubscriptionId,
|
|
179
|
+
status: stripeSub.status,
|
|
180
|
+
stripePriceId: priceId,
|
|
181
|
+
currentPeriodStart: new Date(stripeSub.current_period_start * 1000),
|
|
182
|
+
currentPeriodEnd: new Date(stripeSub.current_period_end * 1000),
|
|
183
|
+
cancelAtPeriodEnd: stripeSub.cancel_at_period_end,
|
|
184
|
+
})
|
|
185
|
+
.onConflictDoUpdate({
|
|
186
|
+
target: subscriptions.stripeSubscriptionId,
|
|
187
|
+
set: {
|
|
188
|
+
status: stripeSub.status,
|
|
189
|
+
stripePriceId: priceId,
|
|
190
|
+
currentPeriodStart: new Date(stripeSub.current_period_start * 1000),
|
|
191
|
+
currentPeriodEnd: new Date(stripeSub.current_period_end * 1000),
|
|
192
|
+
cancelAtPeriodEnd: stripeSub.cancel_at_period_end,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function handleSubscriptionUpdated(
|
|
198
|
+
subscription: Stripe.Subscription
|
|
199
|
+
): Promise<void> {
|
|
200
|
+
const priceId = subscription.items.data[0]?.price.id ?? "";
|
|
201
|
+
|
|
202
|
+
await db
|
|
203
|
+
.update(subscriptions)
|
|
204
|
+
.set({
|
|
205
|
+
status: subscription.status,
|
|
206
|
+
stripePriceId: priceId,
|
|
207
|
+
currentPeriodStart: new Date(subscription.current_period_start * 1000),
|
|
208
|
+
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
|
|
209
|
+
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
|
210
|
+
})
|
|
211
|
+
.where(eq(subscriptions.stripeSubscriptionId, subscription.id));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function handleSubscriptionDeleted(
|
|
215
|
+
subscription: Stripe.Subscription
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
await db
|
|
218
|
+
.update(subscriptions)
|
|
219
|
+
.set({
|
|
220
|
+
status: "canceled",
|
|
221
|
+
cancelAtPeriodEnd: false,
|
|
222
|
+
})
|
|
223
|
+
.where(eq(subscriptions.stripeSubscriptionId, subscription.id));
|
|
224
|
+
}
|