Workers + Hono + D1 + Stripe
了解。**そのままコピペで動く最小構成(実用レベル)**を出します。
構成は👇
-
Cloudflare Workers
-
Hono
-
Cloudflare D1
-
Stripe
■ 完成構成
project/
├ wrangler.toml
├ package.json
└ src/
└ index.ts
■ ① package.json
{
"name": "workers-hono-stripe",
"type": "module",
"dependencies": {
"hono": "^4.0.0",
"stripe": "^14.0.0"
}
}
■ ② wrangler.toml
name = "hono-stripe-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "app-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
[vars]
STRIPE_SECRET_KEY = "sk_test_xxx"
STRIPE_WEBHOOK_SECRET = "whsec_xxx"
■ ③ D1 初期テーブル
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT,
stripe_customer_id TEXT,
created_at TEXT
);
CREATE TABLE subscriptions (
id TEXT PRIMARY KEY,
user_id TEXT,
status TEXT,
price_id TEXT,
current_period_end INTEGER
);
■ ④ src/index.ts(コア)
import { Hono } from 'hono'
import Stripe from 'stripe'
type Bindings = {
DB: D1Database
STRIPE_SECRET_KEY: string
STRIPE_WEBHOOK_SECRET: string
}
const app = new Hono<{ Bindings: Bindings }>()
/* -----------------------------
Stripe 初期化
----------------------------- */
const getStripe = (env: Bindings) => {
return new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2023-10-16'
})
}
/* -----------------------------
ヘルスチェック
----------------------------- */
app.get('/', (c) => {
return c.text('OK')
})
/* -----------------------------
Checkout作成
----------------------------- */
app.post('/create-checkout', async (c) => {
const stripe = getStripe(c.env)
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: 'price_xxx', // ← StripeのPrice ID
quantity: 1
}
],
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel'
})
return c.json({ url: session.url })
})
/* -----------------------------
Webhook(超重要)
----------------------------- */
app.post('/webhook', async (c) => {
const stripe = getStripe(c.env)
const sig = c.req.header('stripe-signature')
const body = await c.req.text()
let event
try {
event = stripe.webhooks.constructEvent(
body,
sig!,
c.env.STRIPE_WEBHOOK_SECRET
)
} catch (err) {
return c.text('Webhook Error', 400)
}
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object as any
await c.env.DB.prepare(
`INSERT INTO subscriptions (id, user_id, status)
VALUES (?, ?, ?)`
)
.bind(session.id, session.customer, 'active')
.run()
break
case 'customer.subscription.deleted':
const sub = event.data.object as any
await c.env.DB.prepare(
`UPDATE subscriptions SET status = ? WHERE id = ?`
)
.bind('canceled', sub.id)
.run()
break
}
return c.text('ok')
})
/* -----------------------------
課金チェック(SaaSガード)
----------------------------- */
app.get('/protected', async (c) => {
const userId = 'test_user' // ← 本来は認証から取得
const result = await c.env.DB.prepare(
`SELECT * FROM subscriptions WHERE user_id = ? AND status = 'active'`
)
.bind(userId)
.first()
if (!result) {
return c.text('Not subscribed', 403)
}
return c.text('Welcome premium user')
})
export default app
■ ⑤ デプロイ手順
npm install
npx wrangler d1 create app-db
npx wrangler publish
■ ⑥ Stripe側設定
やること👇
-
商品作成
-
Price ID取得
-
Webhook設定
-
URL:
https://あなたのworker/workhook -
イベント:
-
checkout.session.completed
-
customer.subscription.deleted
-
-
■ ここが“実務で強いポイント”
このテンプレは👇
-
課金 → Stripe
-
DB → D1
-
API → Hono
-
実行 → Workers
👉 完全にSaaSの基本構造
■ 次にやるべき拡張(重要)
このままだと「荒い」ので👇
-
認証追加(Kinde or Auth0)
-
user_idをStripeと紐付け
-
middlewareで課金ガード
■ ワンランク上(欲しければ出せる)
-
「未課金ユーザー完全遮断middleware」
-
「Stripe Customer自動作成」
-
「Kindeログイン連携」
-
「管理画面(管理者だけ見れる)」
必要なら👇
👉 “本番レベル(課金漏れゼロ設計)”テンプレも出せます