vibes-plug 2.11.0 → 2.14.1
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/.cursor/rules/vibes-plug-core.mdc +3 -3
- package/.cursorrules +3 -3
- package/AGENTS.md +4 -4
- package/BLUEPRINT.md +16 -6
- package/CHANGELOG.md +37 -0
- package/CLAUDE.md +8 -8
- package/README.md +85 -115
- package/index.js +1 -1
- package/package.json +2 -2
- package/plugin.json +2 -2
- package/scripts/check-anti-slop.js +53 -0
- package/scripts/generate_swarm_gif.py +2 -2
- package/skills/ai-llm-integration-expert/SKILL.md +22 -15
- package/skills/ai-prompt-engineering-expert/SKILL.md +133 -83
- package/skills/anti-slop/SKILL.md +133 -0
- package/skills/async-queue-temporal-expert/SKILL.md +135 -158
- package/skills/authentication-identity-expert/SKILL.md +172 -278
- package/skills/brainstorming/SKILL.md +26 -26
- package/skills/database-orm-expert/SKILL.md +164 -303
- package/skills/deep-research-analyst/SKILL.md +136 -0
- package/skills/design-system-architect/SKILL.md +31 -1
- package/skills/email-notification-expert/SKILL.md +31 -4
- package/skills/error-resilience-expert/SKILL.md +21 -0
- package/skills/fullstack-expert/SKILL.md +183 -260
- package/skills/glsl-shader-expert/SKILL.md +190 -107
- package/skills/graph-rag-knowledge-expert/SKILL.md +42 -1
- package/skills/mcp-server-architect/SKILL.md +15 -1
- package/skills/prd-architect/SKILL.md +181 -206
- package/skills/production-ready-hardener/SKILL.md +16 -19
- package/skills/pwa-offline-first-expert/SKILL.md +42 -1
- package/skills/pydantic-ai-expert/SKILL.md +161 -0
- package/skills/saas-architect/SKILL.md +154 -0
- package/skills/senior-frontend/SKILL.md +9 -11
- package/skills/senior-frontend/scripts/frontend_scaffolder.py +1 -1
- package/skills/session-memory-manager/SKILL.md +128 -0
- package/skills/synthetic-data-finetuning-expert/SKILL.md +155 -0
- package/skills/ui-ux-pro-max/SKILL.md +4 -2
- package/skills/vercel-ai-sdk-expert/SKILL.md +181 -0
- package/skills/voice-ai-realtime-agent/SKILL.md +41 -1
- package/skills/web-3d-graphics-expert/SKILL.md +313 -137
- package/skills/web-game-engine-expert/SKILL.md +329 -102
- package/skills/webxr-ar-vr-expert/SKILL.md +162 -123
- package/skills/zero-to-prod-orchestrator/SKILL.md +26 -24
- package/skills/ai-cost-token-optimizer/SKILL.md +0 -82
- package/skills/ai-evals-benchmark-expert/SKILL.md +0 -188
- package/skills/asisten-ramah/SKILL.md +0 -47
- package/skills/auto-doc-updater/SKILL.md +0 -220
- package/skills/autonomous-chaos-monkey/SKILL.md +0 -63
- package/skills/background-jobs-queue-expert/SKILL.md +0 -235
- package/skills/database-migration-versioning-expert/SKILL.md +0 -90
- package/skills/edge-serverless-db-expert/SKILL.md +0 -99
- package/skills/mcp-client-orchestrator/SKILL.md +0 -76
- package/skills/mobile-push-notification-expert/SKILL.md +0 -71
- package/skills/monday-design-aesthetic/SKILL.md +0 -73
- package/skills/project-context-mapper/SKILL.md +0 -85
- package/skills/saas-mvp-launcher/SKILL.md +0 -260
- package/skills/saas-transformer/SKILL.md +0 -500
- package/skills/saas-transformer/references/billing_integration_guide.md +0 -401
- package/skills/self-evolving-memory-graph/SKILL.md +0 -91
- package/skills/session-context-loader/SKILL.md +0 -83
- package/skills/session-handoff-resume/SKILL.md +0 -164
- package/skills/skill-baru/SKILL.md +0 -178
- package/skills/supabase-migration/SKILL.md +0 -91
- package/skills/token-saver/SKILL.md +0 -119
- package/skills/ui-components-expert/SKILL.md +0 -166
- package/skills/vibe-code-gardener/SKILL.md +0 -181
- /package/skills/{saas-transformer → saas-architect}/references/feature_gating_patterns.md +0 -0
- /package/skills/{saas-transformer → saas-architect}/references/saas_transformation_checklist.md +0 -0
- /package/skills/{saas-transformer → saas-architect}/scripts/saas_transformation_scanner.py +0 -0
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
# Billing & Payment Gateway Integration Guide
|
|
2
|
-
|
|
3
|
-
A comprehensive guide to setting up subscription billing, payment gateways, and Merchant of Record (MoR) systems. This guide covers Stripe, Midtrans, PayPal, Paddle, and FastSpring, detailing database structures and webhook verification in a multi-tenant SaaS application.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Gateway Type: Payment Gateway vs. Merchant of Record (MoR)
|
|
8
|
-
|
|
9
|
-
Before choosing a billing system, it is vital to understand the two main categories of billing platforms:
|
|
10
|
-
|
|
11
|
-
| Feature | Payment Gateway (Stripe, Midtrans, PayPal) | Merchant of Record (Paddle, FastSpring) |
|
|
12
|
-
|---|---|---|
|
|
13
|
-
| **Definition** | Processes payments but places legal/financial responsibilities on you. | Acts as the reseller of your software; handles transactions and compliance. |
|
|
14
|
-
| **Sales Tax / VAT** | You must calculate, collect, and file taxes globally (using tools like Stripe Tax). | Automatically handles global taxes, compliance, and invoicing. |
|
|
15
|
-
| **Chargeback Liability**| You manage chargeback disputes directly. | The MoR platform handles chargeback disputes. |
|
|
16
|
-
| **Integration Complexity**| Highly customizable APIs, but requires more backend logic for compliance. | Simple to configure; checkout UI is managed by the MoR. |
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 2. Generic Multi-Gateway Database Schema
|
|
21
|
-
|
|
22
|
-
To support multiple payment gateways, use a generic subscription schema.
|
|
23
|
-
|
|
24
|
-
### Prisma Schema (`prisma/schema.prisma`)
|
|
25
|
-
```prisma
|
|
26
|
-
model Subscription {
|
|
27
|
-
id String @id @default(cuid())
|
|
28
|
-
workspaceId String @unique
|
|
29
|
-
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
30
|
-
gateway String // "stripe", "midtrans", "paypal", "paddle", "fastspring"
|
|
31
|
-
gatewayCustomerId String? @unique
|
|
32
|
-
gatewaySubscriptionId String? @unique
|
|
33
|
-
gatewayPriceId String?
|
|
34
|
-
status String // "active", "trialing", "past_due", "canceled", "unpaid"
|
|
35
|
-
currentPeriodEnd DateTime
|
|
36
|
-
cancelAtPeriodEnd Boolean @default(false)
|
|
37
|
-
createdAt DateTime @default(now())
|
|
38
|
-
updatedAt DateTime @updatedAt
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Drizzle ORM Schema (`lib/db/schema.ts`)
|
|
43
|
-
```typescript
|
|
44
|
-
import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core';
|
|
45
|
-
|
|
46
|
-
export const subscriptions = pgTable('subscriptions', {
|
|
47
|
-
id: text('id').primaryKey(),
|
|
48
|
-
workspaceId: text('workspace_id').notNull().unique(),
|
|
49
|
-
gateway: text('gateway').notNull(), // "stripe", "midtrans", "paypal", "paddle", "fastspring"
|
|
50
|
-
gatewayCustomerId: text('gateway_customer_id').unique(),
|
|
51
|
-
gatewaySubscriptionId: text('gateway_subscription_id').unique(),
|
|
52
|
-
gatewayPriceId: text('gateway_price_id'),
|
|
53
|
-
status: text('status').notNull(),
|
|
54
|
-
currentPeriodEnd: timestamp('current_period_end').notNull(),
|
|
55
|
-
cancelAtPeriodEnd: boolean('cancel_at_period_end').default(false).notNull(),
|
|
56
|
-
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
57
|
-
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
58
|
-
});
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## 3. Webhook Implementations
|
|
64
|
-
|
|
65
|
-
Webhooks are crucial to keep your SaaS database in sync with payment updates. Below are the implementation templates for Next.js 15 (App Router).
|
|
66
|
-
|
|
67
|
-
### A. Stripe Integration
|
|
68
|
-
Processes subscriptions and invoices. Verified via the Stripe SDK.
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
import { NextResponse } from 'next/server';
|
|
72
|
-
import Stripe from 'stripe';
|
|
73
|
-
import { db } from '@/lib/db';
|
|
74
|
-
import { subscriptions } from '@/lib/db/schema';
|
|
75
|
-
import { eq } from 'drizzle-orm';
|
|
76
|
-
|
|
77
|
-
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2025-01-01' as any });
|
|
78
|
-
|
|
79
|
-
export async function POST(req: Request) {
|
|
80
|
-
const body = await req.text();
|
|
81
|
-
const signature = req.headers.get('Stripe-Signature')!;
|
|
82
|
-
|
|
83
|
-
let event: Stripe.Event;
|
|
84
|
-
try {
|
|
85
|
-
event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
|
|
86
|
-
} catch (err: any) {
|
|
87
|
-
return NextResponse.json({ error: `Signature verification failed: ${err.message}` }, { status: 400 });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
switch (event.type) {
|
|
91
|
-
case 'checkout.session.completed': {
|
|
92
|
-
const session = event.data.object as Stripe.Checkout.Session;
|
|
93
|
-
const workspaceId = session.metadata?.workspaceId;
|
|
94
|
-
if (!workspaceId) break;
|
|
95
|
-
|
|
96
|
-
const subscription = await stripe.subscriptions.retrieve(session.subscription as string);
|
|
97
|
-
await db.insert(subscriptions).values({
|
|
98
|
-
id: subscription.id,
|
|
99
|
-
workspaceId,
|
|
100
|
-
gateway: 'stripe',
|
|
101
|
-
gatewayCustomerId: session.customer as string,
|
|
102
|
-
gatewaySubscriptionId: subscription.id,
|
|
103
|
-
gatewayPriceId: subscription.items.data[0].price.id,
|
|
104
|
-
status: subscription.status,
|
|
105
|
-
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
|
|
106
|
-
});
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
case 'invoice.payment_succeeded': {
|
|
110
|
-
const invoice = event.data.object as Stripe.Invoice;
|
|
111
|
-
if (!invoice.subscription) break;
|
|
112
|
-
const subscription = await stripe.subscriptions.retrieve(invoice.subscription as string);
|
|
113
|
-
await db.update(subscriptions)
|
|
114
|
-
.set({
|
|
115
|
-
status: subscription.status,
|
|
116
|
-
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
|
|
117
|
-
})
|
|
118
|
-
.where(eq(subscriptions.gatewaySubscriptionId, subscription.id));
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
case 'customer.subscription.deleted': {
|
|
122
|
-
const subscription = event.data.object as Stripe.Subscription;
|
|
123
|
-
await db.update(subscriptions)
|
|
124
|
-
.set({ status: 'canceled' })
|
|
125
|
-
.where(eq(subscriptions.gatewaySubscriptionId, subscription.id));
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return NextResponse.json({ received: true });
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### B. Midtrans Integration
|
|
134
|
-
Southeast Asia's popular payment gateway. Verified via SHA512 signature key matching.
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
import { NextResponse } from 'next/server';
|
|
138
|
-
import crypto from 'crypto';
|
|
139
|
-
import { db } from '@/lib/db';
|
|
140
|
-
import { subscriptions } from '@/lib/db/schema';
|
|
141
|
-
import { eq } from 'drizzle-orm';
|
|
142
|
-
|
|
143
|
-
export async function POST(req: Request) {
|
|
144
|
-
const body = await req.json();
|
|
145
|
-
const { order_id, status_code, gross_amount, signature_key, transaction_status, fraud_status } = body;
|
|
146
|
-
|
|
147
|
-
// Verify signature key: sha512(order_id + status_code + gross_amount + server_key)
|
|
148
|
-
const serverKey = process.env.MIDTRANS_SERVER_KEY!;
|
|
149
|
-
const hashed = crypto
|
|
150
|
-
.createHash('sha512')
|
|
151
|
-
.update(order_id + status_code + gross_amount + serverKey)
|
|
152
|
-
.digest('hex');
|
|
153
|
-
|
|
154
|
-
if (hashed !== signature_key) {
|
|
155
|
-
return NextResponse.json({ error: 'Invalid Midtrans signature' }, { status: 400 });
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Update subscription/payment state based on transaction_status
|
|
159
|
-
if (transaction_status === 'capture' || transaction_status === 'settlement') {
|
|
160
|
-
if (fraud_status === 'challenge') {
|
|
161
|
-
await db.update(subscriptions).set({ status: 'unpaid' }).where(eq(subscriptions.id, order_id));
|
|
162
|
-
} else {
|
|
163
|
-
await db.update(subscriptions)
|
|
164
|
-
.set({ status: 'active', currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) })
|
|
165
|
-
.where(eq(subscriptions.id, order_id));
|
|
166
|
-
}
|
|
167
|
-
} else if (['cancel', 'deny', 'expire'].includes(transaction_status)) {
|
|
168
|
-
await db.update(subscriptions).set({ status: 'canceled' }).where(eq(subscriptions.id, order_id));
|
|
169
|
-
} else if (transaction_status === 'pending') {
|
|
170
|
-
await db.update(subscriptions).set({ status: 'unpaid' }).where(eq(subscriptions.id, order_id));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return NextResponse.json({ success: true });
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### C. PayPal Integration
|
|
178
|
-
Global subscription billing. Verified via PayPal API request validation.
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
import { NextResponse } from 'next/server';
|
|
182
|
-
import { db } from '@/lib/db';
|
|
183
|
-
import { subscriptions } from '@/lib/db/schema';
|
|
184
|
-
import { eq } from 'drizzle-orm';
|
|
185
|
-
|
|
186
|
-
async function verifyPayPalSignature(headers: Headers, rawBody: string, webhookId: string) {
|
|
187
|
-
// Call PayPal API to verify signature authenticity
|
|
188
|
-
const endpoint = process.env.PAYPAL_MODE === 'live'
|
|
189
|
-
? 'https://api-m.paypal.com'
|
|
190
|
-
: 'https://api-m.sandbox.paypal.com';
|
|
191
|
-
|
|
192
|
-
const authHeader = headers.get('authorization');
|
|
193
|
-
if (!authHeader) return false;
|
|
194
|
-
|
|
195
|
-
const response = await fetch(`${endpoint}/v1/notifications/verify-webhook-signature`, {
|
|
196
|
-
method: 'POST',
|
|
197
|
-
headers: {
|
|
198
|
-
'Content-Type': 'application/json',
|
|
199
|
-
'Authorization': authHeader,
|
|
200
|
-
},
|
|
201
|
-
body: JSON.stringify({
|
|
202
|
-
auth_algo: headers.get('paypal-auth-algo'),
|
|
203
|
-
cert_url: headers.get('paypal-cert-url'),
|
|
204
|
-
transmission_id: headers.get('paypal-transmission-id'),
|
|
205
|
-
transmission_sig: headers.get('paypal-transmission-sig'),
|
|
206
|
-
transmission_time: headers.get('paypal-transmission-time'),
|
|
207
|
-
webhook_id: webhookId,
|
|
208
|
-
webhook_event: JSON.parse(rawBody),
|
|
209
|
-
}),
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const verification = await response.json();
|
|
213
|
-
return verification.verification_status === 'SUCCESS';
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export async function POST(req: Request) {
|
|
217
|
-
const rawBody = await req.text();
|
|
218
|
-
const webhookId = process.env.PAYPAL_WEBHOOK_ID!;
|
|
219
|
-
|
|
220
|
-
const isValid = await verifyPayPalSignature(req.headers, rawBody, webhookId);
|
|
221
|
-
if (!isValid) {
|
|
222
|
-
return NextResponse.json({ error: 'PayPal signature invalid' }, { status: 400 });
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const event = JSON.parse(rawBody);
|
|
226
|
-
|
|
227
|
-
switch (event.event_type) {
|
|
228
|
-
case 'BILLING.SUBSCRIPTION.CREATED': {
|
|
229
|
-
const sub = event.resource;
|
|
230
|
-
const workspaceId = sub.custom_id; // Pass workspace ID in custom_id
|
|
231
|
-
if (!workspaceId) break;
|
|
232
|
-
|
|
233
|
-
await db.insert(subscriptions).values({
|
|
234
|
-
id: sub.id,
|
|
235
|
-
workspaceId,
|
|
236
|
-
gateway: 'paypal',
|
|
237
|
-
gatewayCustomerId: sub.subscriber.payer_id,
|
|
238
|
-
gatewaySubscriptionId: sub.id,
|
|
239
|
-
gatewayPriceId: sub.plan_id,
|
|
240
|
-
status: 'active',
|
|
241
|
-
currentPeriodEnd: new Date(sub.billing_info.next_billing_time),
|
|
242
|
-
});
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
case 'BILLING.SUBSCRIPTION.CANCELLED': {
|
|
246
|
-
const sub = event.resource;
|
|
247
|
-
await db.update(subscriptions)
|
|
248
|
-
.set({ status: 'canceled' })
|
|
249
|
-
.where(eq(subscriptions.gatewaySubscriptionId, sub.id));
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return NextResponse.json({ received: true });
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### D. Paddle Integration (Paddle Billing v2)
|
|
259
|
-
Merchant of Record. Verified locally using an HMAC-SHA256 signature key verification.
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
import { NextResponse } from 'next/server';
|
|
263
|
-
import crypto from 'crypto';
|
|
264
|
-
import { db } from '@/lib/db';
|
|
265
|
-
import { subscriptions } from '@/lib/db/schema';
|
|
266
|
-
import { eq } from 'drizzle-orm';
|
|
267
|
-
|
|
268
|
-
function verifyPaddleSignature(body: string, signatureHeader: string, secret: string): boolean {
|
|
269
|
-
const parts = signatureHeader.split(';');
|
|
270
|
-
const tsPart = parts.find(p => p.startsWith('ts='));
|
|
271
|
-
const h1Part = parts.find(p => p.startsWith('h1='));
|
|
272
|
-
if (!tsPart || !h1Part) return false;
|
|
273
|
-
|
|
274
|
-
const ts = tsPart.split('=')[1];
|
|
275
|
-
const h1 = h1Part.split('=')[1];
|
|
276
|
-
|
|
277
|
-
const payload = `${ts}:${body}`;
|
|
278
|
-
const computedHash = crypto.createHmac('sha256', secret).update(payload).digest('hex');
|
|
279
|
-
return computedHash === h1;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export async function POST(req: Request) {
|
|
283
|
-
const rawBody = await req.text();
|
|
284
|
-
const signature = req.headers.get('Paddle-Signature')!;
|
|
285
|
-
const secret = process.env.PADDLE_WEBHOOK_SECRET!;
|
|
286
|
-
|
|
287
|
-
if (!verifyPaddleSignature(rawBody, signature, secret)) {
|
|
288
|
-
return NextResponse.json({ error: 'Invalid Paddle signature' }, { status: 400 });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const event = JSON.parse(rawBody);
|
|
292
|
-
const eventType = event.event_type;
|
|
293
|
-
const data = event.data;
|
|
294
|
-
|
|
295
|
-
switch (eventType) {
|
|
296
|
-
case 'subscription.created':
|
|
297
|
-
case 'subscription.updated': {
|
|
298
|
-
const workspaceId = data.custom_data?.workspaceId;
|
|
299
|
-
if (!workspaceId) break;
|
|
300
|
-
|
|
301
|
-
await db.insert(subscriptions).values({
|
|
302
|
-
id: data.id,
|
|
303
|
-
workspaceId,
|
|
304
|
-
gateway: 'paddle',
|
|
305
|
-
gatewayCustomerId: data.customer_id,
|
|
306
|
-
gatewaySubscriptionId: data.id,
|
|
307
|
-
gatewayPriceId: data.items[0].price.id,
|
|
308
|
-
status: data.status, // active, trialing, paused
|
|
309
|
-
currentPeriodEnd: new Date(data.current_billing_period.ends_at),
|
|
310
|
-
}).onConflictDoUpdate({
|
|
311
|
-
target: subscriptions.workspaceId,
|
|
312
|
-
set: {
|
|
313
|
-
status: data.status,
|
|
314
|
-
currentPeriodEnd: new Date(data.current_billing_period.ends_at),
|
|
315
|
-
gatewayPriceId: data.items[0].price.id,
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
case 'subscription.canceled': {
|
|
321
|
-
await db.update(subscriptions)
|
|
322
|
-
.set({ status: 'canceled' })
|
|
323
|
-
.where(eq(subscriptions.gatewaySubscriptionId, data.id));
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return NextResponse.json({ received: true });
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### E. FastSpring Integration
|
|
333
|
-
Merchant of Record. Verified via an HMAC-SHA256 signature key verification.
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
import { NextResponse } from 'next/server';
|
|
337
|
-
import crypto from 'crypto';
|
|
338
|
-
import { db } from '@/lib/db';
|
|
339
|
-
import { subscriptions } from '@/lib/db/schema';
|
|
340
|
-
import { eq } from 'drizzle-orm';
|
|
341
|
-
|
|
342
|
-
function verifyFastSpringSignature(body: string, signature: string, secretKey: string): boolean {
|
|
343
|
-
const hash = crypto.createHmac('sha256', secretKey).update(body).digest('base64');
|
|
344
|
-
return hash === signature;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export async function POST(req: Request) {
|
|
348
|
-
const rawBody = await req.text();
|
|
349
|
-
const signature = req.headers.get('X-FS-Signature')!;
|
|
350
|
-
const secretKey = process.env.FASTSPRING_SHARED_SECRET!;
|
|
351
|
-
|
|
352
|
-
if (!verifyFastSpringSignature(rawBody, signature, secretKey)) {
|
|
353
|
-
return NextResponse.json({ error: 'Invalid FastSpring signature' }, { status: 400 });
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const payload = JSON.parse(rawBody);
|
|
357
|
-
|
|
358
|
-
for (const event of payload.events) {
|
|
359
|
-
switch (event.type) {
|
|
360
|
-
case 'subscription.activated': {
|
|
361
|
-
const sub = event.data;
|
|
362
|
-
const workspaceId = sub.tags?.workspaceId;
|
|
363
|
-
if (!workspaceId) break;
|
|
364
|
-
|
|
365
|
-
await db.insert(subscriptions).values({
|
|
366
|
-
id: sub.id,
|
|
367
|
-
workspaceId,
|
|
368
|
-
gateway: 'fastspring',
|
|
369
|
-
gatewayCustomerId: sub.account,
|
|
370
|
-
gatewaySubscriptionId: sub.id,
|
|
371
|
-
gatewayPriceId: sub.product,
|
|
372
|
-
status: 'active',
|
|
373
|
-
currentPeriodEnd: new Date(sub.nextSession),
|
|
374
|
-
});
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
case 'subscription.canceled': {
|
|
378
|
-
const sub = event.data;
|
|
379
|
-
await db.update(subscriptions)
|
|
380
|
-
.set({ status: 'canceled' })
|
|
381
|
-
.where(eq(subscriptions.gatewaySubscriptionId, sub.id));
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return NextResponse.json({ received: true });
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## 4. Best Practices for Webhook Handlers
|
|
394
|
-
|
|
395
|
-
1. **Webhook Idempotency**: Payment gateways can redeliver webhooks. Always store a list of processed event IDs in your database to prevent duplicate actions:
|
|
396
|
-
```typescript
|
|
397
|
-
const processed = await db.query.processedEvents.findFirst({ where: eq(processedEvents.id, eventId) });
|
|
398
|
-
if (processed) return NextResponse.json({ received: true });
|
|
399
|
-
```
|
|
400
|
-
2. **Handle Async Work Gracefully**: Send a fast response (`200 OK`) back to the gateway first, then process any heavy logic asynchronously (or via queue systems like BullMQ) if necessary, to avoid gateway timeouts.
|
|
401
|
-
3. **Database Locks**: Run your update queries inside clean transactions to avoid concurrency anomalies.
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: self-evolving-memory-graph
|
|
3
|
-
description: "Grants the AI long-term episodic memory. The agent autonomously documents the user's coding preferences, past mistakes to avoid, and architectural decisions into a persistent learning graph."
|
|
4
|
-
author: "Roedy Rustam"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Self-Evolving Memory Graph (Episodic Memory)
|
|
8
|
-
|
|
9
|
-
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
<a name="english"></a>
|
|
14
|
-
## English
|
|
15
|
-
|
|
16
|
-
### Orchestration & Integration
|
|
17
|
-
Connects and orchestrates with relevant domain skills like `brainstorming`, `zero-to-prod-orchestrator`, and `project-context-mapper` to ensure cohesive execution.
|
|
18
|
-
|
|
19
|
-
### Description
|
|
20
|
-
Claude forgets everything once a chat is cleared, and its "Project Knowledge" requires manual human updating. This skill gives Antigravity the "superpower" of continuous, autonomous self-improvement. The agent actively listens for user corrections, stylistic preferences, and hard-learned debugging lessons, writing them into a persistent `LEARNING_GRAPH.md` (or `.agents/LEARNING.md`) file. The agent gets smarter and more aligned with the user over time.
|
|
21
|
-
|
|
22
|
-
### Trigger Conditions
|
|
23
|
-
Activate this skill when:
|
|
24
|
-
- The user corrects your code (e.g., "Don't use `any`, use `unknown`", or "Always use Arrow functions").
|
|
25
|
-
- You just spent a long time fixing a very complex bug.
|
|
26
|
-
- The user says "Remember this for the future."
|
|
27
|
-
|
|
28
|
-
### Core Concepts
|
|
29
|
-
|
|
30
|
-
#### 1. Memory Encoding
|
|
31
|
-
Whenever a hard lesson is learned or a strong user preference is stated, do not just say "I will remember that." You must physically write it down.
|
|
32
|
-
Use the `multi_replace_file_content` or `write_to_file` tool to append the lesson to the memory file.
|
|
33
|
-
|
|
34
|
-
#### 2. Memory Format (`LEARNING_GRAPH.md`)
|
|
35
|
-
```markdown
|
|
36
|
-
# 🧠 Self-Evolving Memory Graph
|
|
37
|
-
|
|
38
|
-
## User Stylistic Preferences
|
|
39
|
-
- [2026-08-12] Always use `Tailwind v4 @theme` variables instead of arbitrary values like `text-[#ff0000]`.
|
|
40
|
-
- [2026-08-12] Prefer functional components with `const` over `function` declarations.
|
|
41
|
-
|
|
42
|
-
## Hard-Learned Lessons (Avoid these mistakes)
|
|
43
|
-
- [2026-08-11] Supabase RLS policies: Never use `auth.uid()` in a `FOR ALL` policy without checking role claims. It caused an infinite recursion bug in `users` table.
|
|
44
|
-
|
|
45
|
-
## Architectural Decisions
|
|
46
|
-
- [2026-08-10] State Management: We exclusively use `Zustand` for global state. Do not suggest Redux.
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
#### 3. Memory Retrieval
|
|
50
|
-
Ensure `session-context-loader` is configured to read this memory file at the start of every new chat session.
|
|
51
|
-
|
|
52
|
-
#### 4. Cross-Project Episodic Memory (Global Context)
|
|
53
|
-
- **Continuous Capture**: For complex architectural decisions or bug fixes that span beyond a single project, extract the root cause and solution into a global knowledge graph (e.g., `~/.gemini/knowledge` or equivalent `<appDataDir>/knowledge` KI system).
|
|
54
|
-
- **Contextual Retrieval**: On new sessions, search the global context directory for similar past issues before diving into complex bugs.
|
|
55
|
-
- **Synthesis & Injection**: Inject the historical context directly into the current execution loop, stating explicitly: *"I remember we solved this architectural issue in Project X using technique Y. Applying the same proven pattern here."*
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
### Integration with Other Skills (MANDATORY)
|
|
60
|
-
- `session-context-loader` — Essential for loading the `LEARNING_GRAPH.md` when a new session starts.
|
|
61
|
-
- `project-context-mapper` — The context map tracks *where* things are; the memory graph tracks *how* and *why* things are built.
|
|
62
|
-
|
|
63
|
-
### Referenced By Orchestrators (MANDATORY)
|
|
64
|
-
- `brainstorming` — Add to "Discovery & Audit".
|
|
65
|
-
- `zero-to-prod-orchestrator` — Phase 1 (Discovery & Architecture).
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
<a name="bahasa-indonesia"></a>
|
|
70
|
-
## Bahasa Indonesia
|
|
71
|
-
|
|
72
|
-
### Integrasi Orkestrasi
|
|
73
|
-
Terhubung dan mengorkestrasi skill domain yang relevan seperti `brainstorming`, `zero-to-prod-orchestrator`, dan `project-context-mapper` untuk memastikan eksekusi yang kohesif.
|
|
74
|
-
|
|
75
|
-
### Deskripsi
|
|
76
|
-
Claude melupakan segalanya saat sesi chat baru dimulai. Skill ini memberi Anda "kekuatan super" berupa *Ingatan Jangka Panjang* yang berevolusi sendiri. Agen secara aktif mencatat preferensi koding pengguna, pelajaran dari *bug* yang sulit dipecahkan, dan keputusan arsitektur ke dalam file persisten `LEARNING_GRAPH.md`.
|
|
77
|
-
|
|
78
|
-
### Kondisi Pemicu
|
|
79
|
-
- Saat pengguna mengoreksi gaya koding Anda (misal: "Gunakan bahasa Indonesia baku", "Jangan pakai `var`").
|
|
80
|
-
- Setelah Anda berhasil memecahkan *bug* yang menghabiskan waktu lama.
|
|
81
|
-
- Saat pengguna berkata "Ingat ini untuk proyek ke depannya."
|
|
82
|
-
|
|
83
|
-
### Panduan Singkat
|
|
84
|
-
- **Jangan Hanya Berjanji:** Jika pengguna mengoreksi Anda, jangan sekadar menjawab "Baik, saya akan ingat." LLM tidak punya ingatan bawaan antar-sesi. Anda **wajib** menulis koreksi tersebut ke dalam file `LEARNING_GRAPH.md` di folder `.agents/` atau direktori *root*.
|
|
85
|
-
- **Kategorisasi Ingatan:** Pisahkan ingatan menjadi: Preferensi Gaya (*Style*), Pelajaran Berharga (*Lessons Learned*), dan Keputusan Arsitektur (*Decisions*).
|
|
86
|
-
- **Semakin Lama Semakin Pintar:** Dengan membaca file ini di awal setiap percakapan, Anda tidak akan pernah mengulangi kesalahan yang sama dua kali.
|
|
87
|
-
|
|
88
|
-
### Memori Episodik Lintas-Proyek (Konteks Global)
|
|
89
|
-
- **Penangkapan Berkelanjutan**: Untuk bug yang rumit dan pola arsitektur tingkat lanjut, ekstrak solusi ke dalam grafik pengetahuan global (misal: `~/.gemini/knowledge`).
|
|
90
|
-
- **Pencarian Kontekstual**: Sebelum memecahkan masalah kompleks baru, cari direktori konteks global untuk kasus serupa.
|
|
91
|
-
- **Sintesis & Injeksi**: Secara proaktif sebutkan: *"Saya ingat kita menyelesaikan ini di Proyek X dengan cara Y, saya akan menerapkannya di sini."*
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: session-context-loader
|
|
3
|
-
description: "Automatically loads and learns project context (Tech Stack, PRD, Roadmap, Blueprint) at the start of every new conversation session to ensure focused and directed development / Otomatis memuat dan mempelajari konteks proyek (Tech Stack, PRD, Roadmap, Blueprint) di awal setiap sesi percakapan baru untuk memastikan pengembangan yang terarah dan fokus."
|
|
4
|
-
author: "Roedy Rustam"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Session Context Loader
|
|
8
|
-
|
|
9
|
-
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
<a name="english"></a>
|
|
14
|
-
## English
|
|
15
|
-
|
|
16
|
-
### Orchestration & Integration
|
|
17
|
-
Connects and orchestrates with relevant domain skills like `brainstorming`, `zero-to-prod-orchestrator`, and `project-context-mapper` to ensure cohesive execution.
|
|
18
|
-
|
|
19
|
-
### Description
|
|
20
|
-
This skill ensures that the AI agent is always aligned with the project's core documentation. At the beginning of every new conversation session, it automatically searches for and reads essential project context files (Tech Stack, PRD, Roadmap, Blueprint). This guarantees that development remains focused, directed, and strictly adheres to established requirements and architectural decisions.
|
|
21
|
-
|
|
22
|
-
### Trigger Conditions
|
|
23
|
-
Activate this skill IMMEDIATELY when:
|
|
24
|
-
- It is the very first message of a new conversation session.
|
|
25
|
-
- The user says "start session", "initialize project", or "load context".
|
|
26
|
-
- The AI realizes it lacks the high-level context of the current repository.
|
|
27
|
-
|
|
28
|
-
### Execution Protocol
|
|
29
|
-
|
|
30
|
-
When triggered, you MUST perform the following steps before answering the user's primary request or writing any code:
|
|
31
|
-
|
|
32
|
-
1. **Locate Key Documentation Files:**
|
|
33
|
-
Use your file search tools (`list_dir`, `grep_search`, or `view_file`) to look for the following files in the project root or `.docs/` folder:
|
|
34
|
-
- `PRD.md` (Product Requirements Document)
|
|
35
|
-
- `ROADMAP.md` or `ROADMAP.txt`
|
|
36
|
-
- `BLUEPRINT.md` or `ARCHITECTURE.md`
|
|
37
|
-
- `TECH_STACK.md`, `package.json`, `go.mod`, `Cargo.toml`, or `requirements.txt` (to identify the tech stack)
|
|
38
|
-
|
|
39
|
-
2. **Read and Internalize Context:**
|
|
40
|
-
- Read the contents of the found files.
|
|
41
|
-
- Understand the project's primary goal, current phase in the roadmap, architectural constraints (blueprint), and the specific tech stack being used.
|
|
42
|
-
|
|
43
|
-
3. **Acknowledge and Align:**
|
|
44
|
-
- Briefly acknowledge to the user that the project context has been loaded successfully.
|
|
45
|
-
- State the current Tech Stack and the immediate goal based on the Roadmap/PRD.
|
|
46
|
-
- Proceed with addressing the user's actual request using the newly loaded context.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
<a name="bahasa-indonesia"></a>
|
|
51
|
-
## Bahasa Indonesia
|
|
52
|
-
|
|
53
|
-
### Integrasi Orkestrasi
|
|
54
|
-
Terhubung dan mengorkestrasi skill domain yang relevan seperti `brainstorming`, `zero-to-prod-orchestrator`, dan `project-context-mapper` untuk memastikan eksekusi yang kohesif.
|
|
55
|
-
|
|
56
|
-
### Deskripsi
|
|
57
|
-
Skill ini memastikan bahwa agen AI selalu selaras dengan dokumentasi inti proyek. Di awal setiap sesi percakapan baru, agen akan secara otomatis mencari dan membaca file konteks proyek esensial (Tech Stack, PRD, Roadmap, Blueprint). Ini menjamin bahwa pengembangan proyek aplikasi tetap lebih terarah, fokus, dan sangat mematuhi persyaratan serta keputusan arsitektur yang telah ditetapkan.
|
|
58
|
-
|
|
59
|
-
### Kondisi Pemicu
|
|
60
|
-
Aktifkan skill ini SEGERA ketika:
|
|
61
|
-
- Ini adalah pesan pertama dari sesi percakapan baru.
|
|
62
|
-
- Pengguna berkata "mulai sesi", "inisialisasi proyek", atau "muat konteks".
|
|
63
|
-
- AI menyadari bahwa ia kekurangan konteks tingkat tinggi dari repositori saat ini.
|
|
64
|
-
|
|
65
|
-
### Protokol Eksekusi
|
|
66
|
-
|
|
67
|
-
Saat dipicu, Anda HARUS melakukan langkah-langkah berikut sebelum menjawab permintaan utama pengguna atau menulis kode apa pun:
|
|
68
|
-
|
|
69
|
-
1. **Temukan File Dokumentasi Kunci:**
|
|
70
|
-
Gunakan alat pencarian file Anda (`list_dir`, `grep_search`, atau `view_file`) untuk mencari file berikut di root proyek atau folder `.docs/`:
|
|
71
|
-
- `PRD.md` (Product Requirements Document)
|
|
72
|
-
- `ROADMAP.md` atau `ROADMAP.txt`
|
|
73
|
-
- `BLUEPRINT.md` atau `ARCHITECTURE.md`
|
|
74
|
-
- `TECH_STACK.md`, `package.json`, `go.mod`, `Cargo.toml`, atau `requirements.txt` (untuk mengidentifikasi tech stack)
|
|
75
|
-
|
|
76
|
-
2. **Baca dan Pahami Konteks:**
|
|
77
|
-
- Baca isi dari file-file yang ditemukan.
|
|
78
|
-
- Pahami tujuan utama proyek, fase saat ini di roadmap, batasan arsitektur (blueprint), dan tech stack spesifik yang digunakan.
|
|
79
|
-
|
|
80
|
-
3. **Konfirmasi dan Selaraskan:**
|
|
81
|
-
- Berikan konfirmasi singkat kepada pengguna bahwa konteks proyek telah berhasil dimuat.
|
|
82
|
-
- Sebutkan Tech Stack saat ini dan tujuan langsung berdasarkan Roadmap/PRD.
|
|
83
|
-
- Lanjutkan dengan menjawab permintaan pengguna yang sebenarnya menggunakan konteks yang baru dimuat.
|