stripe-connect
Implement Stripe Connect OAuth for querying connected accounts. Use when onboarding apps to Connect or querying payment/subscription data on their behalf.
安装与使用
请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。
查看来源 ↗使用指南技能说明
Stripe Connect Integration
Stripe Connect enables the platform to query payment and subscription data from connected apps (Total TypeScript, Pro Tailwind, etc.). Each app has their own Stripe account that connects via OAuth.
Critical Architecture: Query, Don't Execute
The platform is the orchestrator ("queen of the hive"), not the executor of financial actions.
┌─────────────────────────────────────────────────────────────┐
│ Support Platform │
│ ("Queen of Hive") │
├─────────────────────────────────────────────────────────────┤
│ We QUERY via Connect │ Apps NOTIFY us via SDK │
│ ─────────────────── │ ───────────────────── │
│ • Payment history │ • Refund processed │
│ • Subscription status │ • Access revoked │
│ • Customer details │ • License transferred │
│ • Dispute info │ • Purchase created │
└─────────────────────────────────────────────────────────────┘
│ │
▼ ▼
Stripe API App Integration
(connected acct) (SDK webhook)
What we DO with Connect:
- Query payment history for a customer
- Check subscription status
- Look up charge details for context
- Verify refund status
What we DON'T do:
- Process refunds (apps do this)
- Create charges
- Modify subscriptions
- Any financial mutations
Why?
- Apps own their financial operations
- Apps notify us when actions complete via SDK
- We provide context/intelligence, not execution
- Simpler, safer, clearer boundaries
OAuth Flow (Standard Accounts)
Step 1: Generate Authorization URL
// Build OAuth URL with state for CSRF protection
function buildConnectAuthUrl(appSlug: string): string {
const state = crypto.randomUUID() // Store in cookie for verification
const params = new URLSearchParams({
response_type: 'code',
client_id: process.env.STRIPE_CONNECT_CLIENT_ID!,
scope: 'read_write',
redirect_uri: `${process.env.NEXT_PUBLIC_URL}/api/stripe/connect/callback`,
state,
})
return `https://connect.stripe.com/oauth/authorize?${params}`
}
Step 2: Handle OAuth Callback
// app/api/stripe/connect/callback/route.ts
export async function GET(request: Request) {
const url = new URL(request.url)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
// Verify state matches cookie (CSRF protection)
// Exchange code for account ID
const response = await stripe.oauth.token({
grant_type: 'authorization_code',
code: code!,
})
const stripeAccountId = response.stripe_user_id // acct_xxx
// Store in apps table
await db.update(appsTable)
.set({
stripe_account_id: stripeAccountId,
stripe_connected: true,
})
.where(eq(appsTable.slug, appSlug))
return Response.redirect('/settings/integrations?success=connected')
}
Querying Connected Accounts
Payment History
async function getPaymentHistory(params: {
stripeAccountId: string
customerEmail: string
limit?: number
}) {
const { stripeAccountId, customerEmail, limit = 10 } = params
// First find the customer on the connected account
const customers = await stripe.customers.list(
{ email: customerEmail, limit: 1 },
{ stripeAccount: stripeAccountId }
)
if (customers.data.length === 0) {
return []
}
const customerId = customers.data[0].id
// Get their charges
const charges = await stripe.charges.list(
{ customer: customerId, limit },
{ stripeAccount: stripeAccountId }
)
return charges.data.map(charge => ({
id: charge.id,
amount: charge.amount,
currency: charge.currency,
status: charge.status,
refunded: charge.refunded,
created: new Date(charge.created * 1000),
description: charge.description,
}))
}
Subscription Status
async function getSubscriptionStatus(params: {
stripeAccountId: string
customerEmail: string
}) {
const { stripeAccountId, customerEmail } = params
const customers = await stripe.customers.list(
{ email: customerEmail, limit: 1 },
{ stripeAccount: stripeAccountId }
)
if (customers.data.length === 0) {
return null
}
const subscriptions = await stripe.subscriptions.list(
{ customer: customers.data[0].id, status: 'all' },
{ stripeAccount: stripeAccountId }
)
return subscriptions.data.map(sub => ({
id: sub.id,
status: sub.status,
currentPeriodEnd: new Date(sub.current_period_end * 1000),
cancelAtPeriodEnd: sub.cancel_at_period_end,
plan: sub.items.data[0]?.price?.nickname,
}))
}
Verify Refund Status
// When app notifies us of a refund, we can verify it
async function verifyRefund(params: {
stripeAccountId: string
refundId: string
}) {
const refund = await stripe.refunds.retrieve(
params.refundId,
{ stripeAccount: params.stripeAccountId }
)
return {
id: refund.id,
status: refund.status,
amount: refund.amount,
chargeId: refund.charge,
}
}
Webhook Monitoring (Minimal)
We only monitor events that require platform awareness:
| Event | Purpose | Action |
|---|---|---|
account.application.deauthorized |
App disconnected | Clear stripe_account_id |
charge.dispute.created |
Dispute opened | Alert + flag conversation |
We do NOT monitor:
-
charge.refunded- Apps notify us via SDK -
customer.subscription.*- Query on-demand -
payment_intent.*- Not our concern
// app/api/stripe/webhooks/route.ts
export async function POST(request: Request) {
const event = stripe.webhooks.constructEvent(body, sig, webhookSecret)
// Only handle deauthorization
if (event.type === 'account.application.deauthorized') {
const accountId = event.account
await db.update(appsTable)
.set({ stripe_account_id: null, stripe_connected: false })
.where(eq(appsTable.stripe_account_id, accountId))
}
return new Response('OK', { status: 200 })
}
Environment Variables
STRIPE_SECRET_KEY=sk_live_xxx # Platform secret key (for Connect API)
STRIPE_CONNECT_CLIENT_ID=ca_xxx # From Connect settings
STRIPE_WEBHOOK_SECRET=whsec_xxx # For deauth webhook only
Database Schema
export const appsTable = pgTable('apps', {
id: text('id').primaryKey(),
slug: text('slug').unique().notNull(),
name: text('name').notNull(),
stripe_account_id: text('stripe_account_id'), // acct_xxx from OAuth
stripe_connected: boolean('stripe_connected').default(false),
// ...
})
Known Connected Accounts
| App | Account ID | Connected |
|---|---|---|
| Total TypeScript | acct_1LFP5yAozSgJZBRP |
✓ |
OAuth Error Reference
| Error | Meaning |
|---|---|
access_denied |
User denied authorization |
invalid_grant |
Code expired/used/invalid |
invalid_redirect_uri |
Redirect URI not whitelisted |
File Locations
| File | Purpose |
|---|---|
apps/web/app/api/stripe/connect/authorize/ |
Start OAuth flow |
apps/web/app/api/stripe/connect/callback/ |
Handle OAuth callback |
apps/web/app/api/stripe/webhooks/ |
Deauth webhook only |
packages/core/src/tools/lookup-stripe.ts |
Query tools (future) |
Reference
- PRD:
docs/support-app-prd/07-stripe-connect.md - Stripe Connect OAuth: https://docs.stripe.com/connect/oauth-reference
使用示例
查看来源仓库中的使用示例与配置说明。
查看来源 ↗来源历史
- 收录时间
- 2026-01-20
- 索引更新时间
- 2026-01-26
- Branch
- main
收录与同步日期属于本站记录。源码修改历史请查看来源仓库。
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!