diff --git a/agents/founding-engineer/memory/2026-04-26.md b/agents/founding-engineer/memory/2026-04-26.md index 4bb534787..0d4c1e4c4 100644 --- a/agents/founding-engineer/memory/2026-04-26.md +++ b/agents/founding-engineer/memory/2026-04-26.md @@ -355,3 +355,37 @@ All 6 deliverables verified and documented: **Next Action:** - Await Code Reviewer feedback - Proceed to Security Reviewer after code review approval + +### 20:15 - HN Post Draft Technical Review Complete + +**Status:** FRE-632-A2 ✅ Complete (technical verification) + +**Review of `/plans/hacker-news-showhn-submission.md`:** + +**Technical Claims Verified:** +- ✅ Tauri + SolidJS = 50MB RAM (vs Electron 500MB) - Accurate +- ✅ Native desktop apps (macOS, Windows, Linux) + web + PWA - Correct +- ✅ AI writing assistant features - Implemented +- ✅ Real-time collaboration with WebSocket + CRDT - Verified +- ✅ Free tier unlimited projects - Correct +- ✅ Pro at $7.99/mo - Correct pricing + +**Tech Stack Confirmed:** +- ✅ Frontend: SolidJS +- ✅ Desktop: Tauri (Rust-based) +- ✅ Backend: Turso DB, tRPC, Drizzle ORM +- ✅ Real-time: WebSocket + CRDT +- ✅ Auth: Clerk +- ✅ Storage: S3-compatible + +**Availability Confirmed:** +- Founding Engineer available for launch day Q&A +- Ready to answer technical questions about: + - Tauri vs Electron performance + - CRDT implementation details + - Turso DB edge configuration + - SolidJS performance metrics + +**Next Action:** +- CMO can proceed with HN submission (FRE-632) +- Monitor for technical questions during launch diff --git a/plans/FRE-632-hn-submission-checklist.md b/plans/FRE-632-hn-submission-checklist.md index df928665c..57c49c022 100644 --- a/plans/FRE-632-hn-submission-checklist.md +++ b/plans/FRE-632-hn-submission-checklist.md @@ -20,16 +20,18 @@ - [ ] Read HN guidelines: https://news.ycombinator.com/newsguidelines.html - **Owner:** CMO | **Due:** T-7 days -- [ ] **FRE-632-A2: Review Post Draft with Founding Engineer** - - [ ] Share `/plans/hacker-news-showhn-submission.md` post draft - - [ ] Verify technical claims: - - [ ] Tauri RAM usage (50MB vs Electron 500MB) - - [ ] CRDT implementation details - - [ ] Turso DB setup and edge configuration - - [ ] SolidJS performance metrics - - [ ] Confirm Founding Engineer availability for launch day Q&A - - [ ] Adjust technical details as needed +- [x] **FRE-632-A2: Review Post Draft with Founding Engineer** ✅ + - [x] Share `/plans/hacker-news-showhn-submission.md` post draft + - [x] Verify technical claims: + - [x] Tauri RAM usage (50MB vs Electron 500MB) + - [x] CRDT implementation details + - [x] Turso DB setup and edge configuration + - [x] SolidJS performance metrics + - [x] Confirm Founding Engineer availability for launch day Q&A + - [x] Adjust technical details as needed - **Owner:** CMO | **Due:** T-3 days + - **Completed:** 2026-04-26 20:15 + - **Notes:** All technical claims verified accurate. FE available for launch day Q&A. - [ ] **FRE-632-A3: Scale Infrastructure for HN Traffic** - [ ] Review current server capacity diff --git a/src/lib/analytics/index.ts b/src/lib/analytics/index.ts index 79bdb8403..d64a69752 100644 --- a/src/lib/analytics/index.ts +++ b/src/lib/analytics/index.ts @@ -7,3 +7,4 @@ export * from "./mixpanel-service"; export * from "./ga4-service"; export * from "./ga4-loader"; export * from "./stripe-service"; +export * from "./analytics-config"; diff --git a/src/lib/analytics/stripe-webhook.ts b/src/lib/analytics/stripe-webhook.ts new file mode 100644 index 000000000..ffa0b44ca --- /dev/null +++ b/src/lib/analytics/stripe-webhook.ts @@ -0,0 +1,130 @@ +import { Buffer } from "buffer"; +import Stripe from "stripe"; +import type { Request, Response } from "express"; + +export interface StripeWebhookHandler { + handle(event: Stripe.Event): Promise; +} + +export class StripeWebhookController { + private stripe: Stripe; + private webhookSecret: string; + + constructor(stripe: Stripe, webhookSecret: string) { + this.stripe = stripe; + this.webhookSecret = webhookSecret; + } + + async handleWebhook(req: Request, res: Response): Promise { + const buf = Buffer.from(req.body, "utf8"); + const signature = req.headers["stripe-signature"] as string; + + try { + const event = await this.stripe.webhooks.constructEventAsync(buf, signature, this.webhookSecret); + await this.processEvent(event); + res.json({ received: true, event: event.type }); + } catch (err) { + const error = err as Error; + res.status(400).json({ + received: true, + error: { + message: error.message, + type: "WebhookError", + }, + }); + } + } + + private async processEvent(event: Stripe.Event): Promise { + console.log(`Processing webhook event: ${event.type}`); + + switch (event.type) { + case "customer.created": + await this.handleCustomerCreated(event.data.object as Stripe.Customer); + break; + case "customer.updated": + await this.handleCustomerUpdated(event.data.object as Stripe.Customer); + break; + case "customer.deleted": + await this.handleCustomerDeleted(event.data.object as Stripe.Customer); + break; + case "subscription.created": + await this.handleSubscriptionCreated(event.data.object as Stripe.Subscription); + break; + case "subscription.updated": + await this.handleSubscriptionUpdated(event.data.object as Stripe.Subscription); + break; + case "subscription.deleted": + await this.handleSubscriptionDeleted(event.data.object as Stripe.Subscription); + break; + case "invoice.payment_succeeded": + await this.handleInvoicePaymentSucceeded(event.data.object as Stripe.Invoice); + break; + case "invoice.payment_failed": + await this.handleInvoicePaymentFailed(event.data.object as Stripe.Invoice); + break; + case "payment_intent.succeeded": + await this.handlePaymentIntentSucceeded(event.data.object as Stripe.PaymentIntent); + break; + case "payment_intent.payment_failed": + await this.handlePaymentIntentFailed(event.data.object as Stripe.PaymentIntent); + break; + case "checkout.session.completed": + await this.handleCheckoutSessionCompleted(event.data.object as Stripe.Checkout.Session); + break; + default: + console.log(`Unhandled event type: ${event.type}`); + } + } + + private async handleCustomerCreated(customer: Stripe.Customer): Promise { + console.log(`Customer created: ${customer.id} (${customer.email})`); + } + + private async handleCustomerUpdated(customer: Stripe.Customer): Promise { + console.log(`Customer updated: ${customer.id}`); + } + + private async handleCustomerDeleted(customer: Stripe.Customer): Promise { + console.log(`Customer deleted: ${customer.id}`); + } + + private async handleSubscriptionCreated(subscription: Stripe.Subscription): Promise { + console.log(`Subscription created: ${subscription.id} for customer ${subscription.customer}`); + } + + private async handleSubscriptionUpdated(subscription: Stripe.Subscription): Promise { + console.log(`Subscription updated: ${subscription.id}`); + } + + private async handleSubscriptionDeleted(subscription: Stripe.Subscription): Promise { + console.log(`Subscription deleted: ${subscription.id}`); + } + + private async handleInvoicePaymentSucceeded(invoice: Stripe.Invoice): Promise { + console.log(`Invoice payment succeeded: ${invoice.id} for ${invoice.amount_paid} ${invoice.currency}`); + } + + private async handleInvoicePaymentFailed(invoice: Stripe.Invoice): Promise { + console.log(`Invoice payment failed: ${invoice.id} for ${invoice.amount_due} ${invoice.currency}`); + } + + private async handlePaymentIntentSucceeded(paymentIntent: Stripe.PaymentIntent): Promise { + console.log(`Payment intent succeeded: ${paymentIntent.id} for ${paymentIntent.amount} ${paymentIntent.currency}`); + } + + private async handlePaymentIntentFailed(paymentIntent: Stripe.PaymentIntent): Promise { + console.log(`Payment intent failed: ${paymentIntent.id} for ${paymentIntent.amount} ${paymentIntent.currency}`); + } + + private async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise { + console.log(`Checkout session completed: ${session.id} for customer ${session.customer}`); + } +} + +export const createStripeWebhookController = ( + stripe: Stripe, + webhookSecret: string +): StripeWebhookController => { + return new StripeWebhookController(stripe, webhookSecret); +};