@riligar/agents-kit 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/skills/riligar-business-startup/SKILL.md +0 -1
- package/.agent/skills/riligar-design-system/SKILL.md +0 -1
- package/.agent/skills/riligar-dev-architecture/SKILL.md +7 -8
- package/.agent/skills/riligar-dev-auth-elysia/SKILL.md +7 -3
- package/.agent/skills/riligar-dev-auth-react/SKILL.md +5 -3
- package/.agent/skills/riligar-dev-autopilot/SKILL.md +0 -1
- package/.agent/skills/riligar-dev-backend/SKILL.md +0 -1
- package/.agent/skills/riligar-dev-clean-code/SKILL.md +0 -1
- package/.agent/skills/riligar-dev-code-review/SKILL.md +0 -1
- package/.agent/skills/riligar-dev-database/SKILL.md +8 -9
- package/.agent/skills/riligar-dev-frontend/SKILL.md +0 -1
- package/.agent/skills/riligar-dev-landing-page/SKILL.md +0 -1
- package/.agent/skills/riligar-dev-seo/SKILL.md +6 -3
- package/.agent/skills/riligar-dev-stripe/SKILL.md +196 -91
- package/.agent/skills/riligar-dev-stripe/assets/stripe-client.js +422 -0
- package/.agent/skills/riligar-dev-stripe/assets/stripe-server.js +300 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-database.md +369 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-elysia.md +342 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-react.md +478 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-webhooks.md +376 -0
- package/.agent/skills/riligar-infrastructure/SKILL.md +0 -1
- package/.agent/skills/riligar-marketing-copy/SKILL.md +0 -1
- package/.agent/skills/riligar-marketing-email/SKILL.md +0 -1
- package/.agent/skills/riligar-marketing-seo/SKILL.md +0 -1
- package/.agent/skills/riligar-plan-writing/SKILL.md +0 -1
- package/.agent/skills/riligar-tech-stack/SKILL.md +0 -1
- package/.agent/skills/skill-creator/SKILL.md +0 -2
- package/package.json +1 -1
- /package/.agent/skills/riligar-dev-architecture/{context-discovery.md → references/context-discovery.md} +0 -0
- /package/.agent/skills/riligar-dev-architecture/{examples.md → references/examples.md} +0 -0
- /package/.agent/skills/riligar-dev-architecture/{pattern-selection.md → references/pattern-selection.md} +0 -0
- /package/.agent/skills/riligar-dev-architecture/{patterns-reference.md → references/patterns-reference.md} +0 -0
- /package/.agent/skills/riligar-dev-architecture/{trade-off-analysis.md → references/trade-off-analysis.md} +0 -0
- /package/.agent/skills/riligar-dev-database/{database-selection.md → references/database-selection.md} +0 -0
- /package/.agent/skills/riligar-dev-database/{indexing.md → references/indexing.md} +0 -0
- /package/.agent/skills/riligar-dev-database/{migrations.md → references/migrations.md} +0 -0
- /package/.agent/skills/riligar-dev-database/{optimization.md → references/optimization.md} +0 -0
- /package/.agent/skills/riligar-dev-database/{orm-selection.md → references/orm-selection.md} +0 -0
- /package/.agent/skills/riligar-dev-database/{schema-design.md → references/schema-design.md} +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Stripe Webhooks
|
|
2
|
+
|
|
3
|
+
Complete patterns for handling Stripe webhook events in Elysia.
|
|
4
|
+
|
|
5
|
+
## Webhook Endpoint
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// routes/webhook.js
|
|
9
|
+
import { Elysia } from 'elysia'
|
|
10
|
+
import Stripe from 'stripe'
|
|
11
|
+
|
|
12
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
|
|
13
|
+
|
|
14
|
+
export const webhookRoutes = new Elysia()
|
|
15
|
+
.post('/webhook/stripe', async ({ request, set }) => {
|
|
16
|
+
const sig = request.headers.get('stripe-signature')
|
|
17
|
+
const body = await request.text()
|
|
18
|
+
|
|
19
|
+
let event
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
event = stripe.webhooks.constructEvent(
|
|
23
|
+
body,
|
|
24
|
+
sig,
|
|
25
|
+
process.env.STRIPE_WEBHOOK_SECRET
|
|
26
|
+
)
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error('Webhook signature verification failed:', err.message)
|
|
29
|
+
set.status = 400
|
|
30
|
+
return { error: 'Invalid signature' }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle the event
|
|
34
|
+
try {
|
|
35
|
+
await handleWebhookEvent(event)
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('Webhook handler error:', err)
|
|
38
|
+
set.status = 500
|
|
39
|
+
return { error: 'Webhook handler failed' }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { received: true }
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Essential Events
|
|
47
|
+
|
|
48
|
+
| Event | When | Action |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `checkout.session.completed` | User completes checkout | Provision access, create records |
|
|
51
|
+
| `customer.subscription.created` | New subscription | Store subscription ID |
|
|
52
|
+
| `customer.subscription.updated` | Plan change, renewal | Update plan/status |
|
|
53
|
+
| `customer.subscription.deleted` | Subscription ends | Revoke access |
|
|
54
|
+
| `invoice.paid` | Successful payment | Update billing history |
|
|
55
|
+
| `invoice.payment_failed` | Payment failed | Notify user, retry logic |
|
|
56
|
+
| `customer.created` | New customer | Store customer ID |
|
|
57
|
+
|
|
58
|
+
## Event Handler
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
// services/webhook-handlers.js
|
|
62
|
+
import { db } from '../database'
|
|
63
|
+
import { users, subscriptions } from '../database/schema'
|
|
64
|
+
import { eq } from 'drizzle-orm'
|
|
65
|
+
|
|
66
|
+
export async function handleWebhookEvent(event) {
|
|
67
|
+
switch (event.type) {
|
|
68
|
+
case 'checkout.session.completed':
|
|
69
|
+
await handleCheckoutComplete(event.data.object)
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
case 'customer.subscription.created':
|
|
73
|
+
case 'customer.subscription.updated':
|
|
74
|
+
await handleSubscriptionChange(event.data.object)
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
case 'customer.subscription.deleted':
|
|
78
|
+
await handleSubscriptionDeleted(event.data.object)
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
case 'invoice.paid':
|
|
82
|
+
await handleInvoicePaid(event.data.object)
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
case 'invoice.payment_failed':
|
|
86
|
+
await handlePaymentFailed(event.data.object)
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
default:
|
|
90
|
+
console.log(`Unhandled event type: ${event.type}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Handler Functions
|
|
96
|
+
|
|
97
|
+
### Checkout Complete
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
async function handleCheckoutComplete(session) {
|
|
101
|
+
const { customer, subscription, metadata } = session
|
|
102
|
+
const userId = metadata.userId
|
|
103
|
+
|
|
104
|
+
// Update user with Stripe IDs
|
|
105
|
+
await db.update(users)
|
|
106
|
+
.set({
|
|
107
|
+
stripeCustomerId: customer,
|
|
108
|
+
stripeSubscriptionId: subscription,
|
|
109
|
+
plan: 'pro', // or extract from session
|
|
110
|
+
updatedAt: new Date()
|
|
111
|
+
})
|
|
112
|
+
.where(eq(users.id, userId))
|
|
113
|
+
|
|
114
|
+
// Optional: send welcome email
|
|
115
|
+
// await sendWelcomeEmail(userId)
|
|
116
|
+
|
|
117
|
+
console.log(`Checkout completed for user ${userId}`)
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Subscription Change
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
async function handleSubscriptionChange(subscription) {
|
|
125
|
+
const { id, status, items, current_period_end, cancel_at_period_end } = subscription
|
|
126
|
+
const priceId = items.data[0].price.id
|
|
127
|
+
|
|
128
|
+
// Find user by subscription ID
|
|
129
|
+
const [user] = await db.select()
|
|
130
|
+
.from(users)
|
|
131
|
+
.where(eq(users.stripeSubscriptionId, id))
|
|
132
|
+
.limit(1)
|
|
133
|
+
|
|
134
|
+
if (!user) {
|
|
135
|
+
console.error(`No user found for subscription ${id}`)
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Map price ID to plan name
|
|
140
|
+
const planMap = {
|
|
141
|
+
'price_starter_monthly': 'starter',
|
|
142
|
+
'price_pro_monthly': 'pro',
|
|
143
|
+
'price_enterprise_monthly': 'enterprise'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await db.update(users)
|
|
147
|
+
.set({
|
|
148
|
+
plan: planMap[priceId] || 'free',
|
|
149
|
+
subscriptionStatus: status,
|
|
150
|
+
currentPeriodEnd: new Date(current_period_end * 1000),
|
|
151
|
+
cancelAtPeriodEnd: cancel_at_period_end,
|
|
152
|
+
updatedAt: new Date()
|
|
153
|
+
})
|
|
154
|
+
.where(eq(users.id, user.id))
|
|
155
|
+
|
|
156
|
+
console.log(`Subscription ${id} updated to ${status}`)
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Subscription Deleted
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
async function handleSubscriptionDeleted(subscription) {
|
|
164
|
+
const { id } = subscription
|
|
165
|
+
|
|
166
|
+
await db.update(users)
|
|
167
|
+
.set({
|
|
168
|
+
plan: 'free',
|
|
169
|
+
subscriptionStatus: 'canceled',
|
|
170
|
+
stripeSubscriptionId: null,
|
|
171
|
+
updatedAt: new Date()
|
|
172
|
+
})
|
|
173
|
+
.where(eq(users.stripeSubscriptionId, id))
|
|
174
|
+
|
|
175
|
+
console.log(`Subscription ${id} deleted`)
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Invoice Paid
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
async function handleInvoicePaid(invoice) {
|
|
183
|
+
const { customer, amount_paid, id, hosted_invoice_url } = invoice
|
|
184
|
+
|
|
185
|
+
// Find user by customer ID
|
|
186
|
+
const [user] = await db.select()
|
|
187
|
+
.from(users)
|
|
188
|
+
.where(eq(users.stripeCustomerId, customer))
|
|
189
|
+
.limit(1)
|
|
190
|
+
|
|
191
|
+
if (!user) return
|
|
192
|
+
|
|
193
|
+
// Store invoice record (optional)
|
|
194
|
+
await db.insert(invoices).values({
|
|
195
|
+
userId: user.id,
|
|
196
|
+
stripeInvoiceId: id,
|
|
197
|
+
amount: amount_paid,
|
|
198
|
+
status: 'paid',
|
|
199
|
+
url: hosted_invoice_url,
|
|
200
|
+
createdAt: new Date()
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
console.log(`Invoice ${id} paid: ${amount_paid}`)
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Payment Failed
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
async function handlePaymentFailed(invoice) {
|
|
211
|
+
const { customer, attempt_count, next_payment_attempt } = invoice
|
|
212
|
+
|
|
213
|
+
const [user] = await db.select()
|
|
214
|
+
.from(users)
|
|
215
|
+
.where(eq(users.stripeCustomerId, customer))
|
|
216
|
+
.limit(1)
|
|
217
|
+
|
|
218
|
+
if (!user) return
|
|
219
|
+
|
|
220
|
+
// Update subscription status
|
|
221
|
+
await db.update(users)
|
|
222
|
+
.set({
|
|
223
|
+
subscriptionStatus: 'past_due',
|
|
224
|
+
updatedAt: new Date()
|
|
225
|
+
})
|
|
226
|
+
.where(eq(users.id, user.id))
|
|
227
|
+
|
|
228
|
+
// Send notification
|
|
229
|
+
// await sendPaymentFailedEmail(user.email, {
|
|
230
|
+
// attemptCount: attempt_count,
|
|
231
|
+
// nextAttempt: next_payment_attempt
|
|
232
|
+
// })
|
|
233
|
+
|
|
234
|
+
console.log(`Payment failed for customer ${customer}, attempt ${attempt_count}`)
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Idempotency
|
|
239
|
+
|
|
240
|
+
Webhooks may be sent multiple times. Handle idempotency:
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
async function handleCheckoutComplete(session) {
|
|
244
|
+
const { id, metadata } = session
|
|
245
|
+
const userId = metadata.userId
|
|
246
|
+
|
|
247
|
+
// Check if already processed
|
|
248
|
+
const [existing] = await db.select()
|
|
249
|
+
.from(webhookEvents)
|
|
250
|
+
.where(eq(webhookEvents.stripeEventId, id))
|
|
251
|
+
.limit(1)
|
|
252
|
+
|
|
253
|
+
if (existing) {
|
|
254
|
+
console.log(`Event ${id} already processed`)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Record event
|
|
259
|
+
await db.insert(webhookEvents).values({
|
|
260
|
+
stripeEventId: id,
|
|
261
|
+
type: 'checkout.session.completed',
|
|
262
|
+
processedAt: new Date()
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Process the event
|
|
266
|
+
await db.update(users)
|
|
267
|
+
.set({
|
|
268
|
+
stripeCustomerId: session.customer,
|
|
269
|
+
stripeSubscriptionId: session.subscription,
|
|
270
|
+
plan: 'pro'
|
|
271
|
+
})
|
|
272
|
+
.where(eq(users.id, userId))
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Testing Locally
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Install Stripe CLI
|
|
280
|
+
brew install stripe/stripe-cli/stripe
|
|
281
|
+
|
|
282
|
+
# Login to Stripe
|
|
283
|
+
stripe login
|
|
284
|
+
|
|
285
|
+
# Forward webhooks to local server
|
|
286
|
+
stripe listen --forward-to localhost:3000/webhook/stripe
|
|
287
|
+
|
|
288
|
+
# Copy the webhook signing secret (whsec_...) to .env.development
|
|
289
|
+
|
|
290
|
+
# Trigger specific events
|
|
291
|
+
stripe trigger checkout.session.completed
|
|
292
|
+
stripe trigger customer.subscription.updated
|
|
293
|
+
stripe trigger invoice.payment_failed
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Webhook Event Types Reference
|
|
297
|
+
|
|
298
|
+
### Checkout Events
|
|
299
|
+
|
|
300
|
+
| Event | Data Object |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| `checkout.session.completed` | Session with customer, subscription, metadata |
|
|
303
|
+
| `checkout.session.expired` | Session that expired |
|
|
304
|
+
|
|
305
|
+
### Subscription Events
|
|
306
|
+
|
|
307
|
+
| Event | Data Object |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| `customer.subscription.created` | Full subscription object |
|
|
310
|
+
| `customer.subscription.updated` | Full subscription with changes |
|
|
311
|
+
| `customer.subscription.deleted` | Subscription being canceled |
|
|
312
|
+
| `customer.subscription.trial_will_end` | 3 days before trial ends |
|
|
313
|
+
|
|
314
|
+
### Invoice Events
|
|
315
|
+
|
|
316
|
+
| Event | Data Object |
|
|
317
|
+
| --- | --- |
|
|
318
|
+
| `invoice.paid` | Invoice with payment details |
|
|
319
|
+
| `invoice.payment_failed` | Invoice with failure reason |
|
|
320
|
+
| `invoice.upcoming` | Preview of next invoice |
|
|
321
|
+
| `invoice.finalized` | Invoice ready for payment |
|
|
322
|
+
|
|
323
|
+
### Payment Events
|
|
324
|
+
|
|
325
|
+
| Event | Data Object |
|
|
326
|
+
| --- | --- |
|
|
327
|
+
| `payment_intent.succeeded` | PaymentIntent completed |
|
|
328
|
+
| `payment_intent.payment_failed` | PaymentIntent failed |
|
|
329
|
+
|
|
330
|
+
## Error Handling
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
export const webhookRoutes = new Elysia()
|
|
334
|
+
.post('/webhook/stripe', async ({ request, set }) => {
|
|
335
|
+
const sig = request.headers.get('stripe-signature')
|
|
336
|
+
const body = await request.text()
|
|
337
|
+
|
|
338
|
+
let event
|
|
339
|
+
|
|
340
|
+
// Verify signature
|
|
341
|
+
try {
|
|
342
|
+
event = stripe.webhooks.constructEvent(
|
|
343
|
+
body,
|
|
344
|
+
sig,
|
|
345
|
+
process.env.STRIPE_WEBHOOK_SECRET
|
|
346
|
+
)
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error('Invalid signature:', err.message)
|
|
349
|
+
set.status = 400
|
|
350
|
+
return { error: 'Invalid signature' }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Handle event with error catching
|
|
354
|
+
try {
|
|
355
|
+
await handleWebhookEvent(event)
|
|
356
|
+
return { received: true }
|
|
357
|
+
} catch (err) {
|
|
358
|
+
console.error('Handler error:', err)
|
|
359
|
+
|
|
360
|
+
// Return 200 to prevent Stripe retries for permanent failures
|
|
361
|
+
// Return 500 to trigger Stripe retry for temporary failures
|
|
362
|
+
if (isPermanentFailure(err)) {
|
|
363
|
+
console.error('Permanent failure, not retrying')
|
|
364
|
+
return { received: true, error: err.message }
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
set.status = 500
|
|
368
|
+
return { error: 'Internal error' }
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
function isPermanentFailure(err) {
|
|
373
|
+
// User not found, invalid data, etc.
|
|
374
|
+
return err.code === 'USER_NOT_FOUND' || err.code === 'INVALID_DATA'
|
|
375
|
+
}
|
|
376
|
+
```
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: riligar-marketing-copy
|
|
3
|
-
type: marketing
|
|
4
3
|
description: Generate compelling marketing copy using the Elevated Direct Response framework. Use when creating landing pages, headlines, email campaigns, ad copy, CTAs, value propositions, or any persuasive marketing content.
|
|
5
4
|
---
|
|
6
5
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: riligar-marketing-email
|
|
3
|
-
type: marketing
|
|
4
3
|
description: Email sequence design and optimization. Use when creating drip campaigns, automated flows, nurture sequences, onboarding emails, welcome sequences, re-engagement emails, or lifecycle email programs.
|
|
5
4
|
---
|
|
6
5
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: riligar-marketing-seo
|
|
3
|
-
type: marketing
|
|
4
3
|
description: Design and evaluate programmatic SEO strategies for creating SEO-driven pages at scale. Use when working with programmatic SEO, template pages, directory pages, location pages, or keyword-pattern page generation.
|
|
5
4
|
---
|
|
6
5
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-creator
|
|
3
|
-
type: meta
|
|
4
3
|
description: Guide for creating effective skills. Use when creating a new skill or updating an existing skill that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
|
|
5
4
|
---
|
|
6
5
|
|
|
@@ -337,7 +336,6 @@ Write the YAML frontmatter with `name`, `type`, and `description`:
|
|
|
337
336
|
```yaml
|
|
338
337
|
---
|
|
339
338
|
name: riligar-dev-example
|
|
340
|
-
type: development
|
|
341
339
|
description: Brief description of what the skill does. Use when [specific triggers].
|
|
342
340
|
---
|
|
343
341
|
```
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/.agent/skills/riligar-dev-database/{orm-selection.md → references/orm-selection.md}
RENAMED
|
File without changes
|
/package/.agent/skills/riligar-dev-database/{schema-design.md → references/schema-design.md}
RENAMED
|
File without changes
|