最短で安全にカード決済を導入するなら、Stripeのホスト型UI「Checkout」が便利です。バックエンドでセッションを作成し、フロント(Vue)からStripe.jsでリダイレクトするだけで決済画面を提供できます。この記事では、Vue 3 + Node/Express を例に、Checkout セッション作成 → 決済ページへ遷移 → Webhookで支払い確定を受け取るまでの実装手順をまとめます。
全体像
- バックエンド:
/create-checkout-session
を実装(秘密鍵使用) - フロント(Vue):ボタン押下で上記APIを叩き、Stripe Checkoutへリダイレクト
- Webhook:決済完了イベント(
checkout.session.completed
)を受信し、注文確定処理を実行
前提準備
- Stripeアカウント(テストモードでOK)
- 価格ID(ダッシュボードで商品作成 → 価格ID
price_xxx
を取得) - 公開鍵(
pk_test_...
)と秘密鍵(sk_test_...
)
バックエンド(Node/Express)
1) 依存関係のインストール
npm i express cors stripe dotenv
2) 環境変数
# .env
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx # 後述のCLIで取得(開発時)
CLIENT_URL=http://localhost:5173 # VueのURL
3) Expressサーバ(Checkoutセッション作成)
// server.js
import express from 'express'
import cors from 'cors'
import dotenv from 'dotenv'
import Stripe from 'stripe'
dotenv.config()
const app = express()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20' })
app.use(cors({ origin: process.env.CLIENT_URL }))
app.use(express.json())
app.post('/create-checkout-session', async (req, res) => {
try {
const { priceId, quantity = 1, customer_email, metadata = {} } = req.body
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: priceId, quantity }],
success_url: `${process.env.CLIENT_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.CLIENT_URL}/cancel`,
customer_email,
metadata, // 例: { orderId: 'abc123' }
// 追加オプション例:shipping_address_collection, automatic_tax など
})
res.json({ id: session.id, url: session.url })
} catch (e) {
console.error(e)
res.status(500).json({ error: 'Failed to create session' })
}
})
app.listen(3000, () => console.log('API listening on http://localhost:3000'))
フロントエンド(Vue 3)
1) 依存関係
npm i @stripe/stripe-js
2) 決済ボタンの実装(Checkoutへリダイレクト)
<template>
<button @click="checkout" :disabled="loading">
{{ loading ? '処理中...' : '決済へ進む' }}
</button>
</template>
<script setup>
import { ref } from 'vue'
import { loadStripe } from '@stripe/stripe-js'
const loading = ref(false)
// 公開鍵(ダッシュボード > 開発者 > APIキー)
const stripePromise = loadStripe('pk_test_xxx')
async function checkout() {
loading.value = true
try {
const resp = await fetch('http://localhost:3000/create-checkout-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
priceId: 'price_xxx', // Stripeダッシュボードで作成した価格ID
quantity: 1,
customer_email: 'test@example.com',
metadata: { orderId: 'ORD-1001' }
})
})
const data = await resp.json()
// 1) リダイレクトURLが返る場合は直接遷移
if (data.url) {
window.location.href = data.url
return
}
// 2) idのみ返す実装にした場合は、Stripe.jsで遷移
const stripe = await stripePromise
await stripe.redirectToCheckout({ sessionId: data.id })
} catch (e) {
alert('セッション作成に失敗しました')
} finally {
loading.value = false
}
}
</script>
3) 結果ページ(成功/キャンセル)
<!-- pages/Success.vue -->
<template>
<h1>決済に成功しました</h1>
<p>ご購入ありがとうございます。</p>
</template>
Webhookで支払い確定を受け取る
クライアントの遷移だけでは改ざんの恐れがあるため、バックエンドでWebhookを受信し、サーバー側で注文確定するのが推奨です。
1) Stripe CLI(開発時)
# Stripe CLIを起動しローカルに転送
stripe listen --forward-to localhost:3001/webhook
# 表示された Signing secret を .env の STRIPE_WEBHOOK_SECRET に設定
2) Webhookサーバ(生ボディで検証)
npm i express raw-body
// webhook.js
import express from 'express'
import dotenv from 'dotenv'
import Stripe from 'stripe'
import getRawBody from 'raw-body'
dotenv.config()
const app = express()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20' })
// 署名検証のため JSON パーサは使わず raw を受け取る
app.post('/webhook', async (req, res) => {
let event
try {
const sig = req.headers['stripe-signature']
const raw = await getRawBody(req)
event = stripe.webhooks.constructEvent(raw, sig, process.env.STRIPE_WEBHOOK_SECRET)
} catch (err) {
console.error('Webhook signature verification failed.', err.message)
return res.sendStatus(400)
}
try {
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object
// ここで注文確定処理:在庫引当、メール送信、DB更新など
// session.metadata.orderId などで自システムの参照が可能
break
}
default:
// 必要に応じて他のイベントも処理
break
}
res.sendStatus(200)
} catch (e) {
console.error(e)
res.sendStatus(500)
}
})
app.listen(3001, () => console.log('Webhook server on http://localhost:3001'))
よく使うCheckoutオプション
await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: 'price_xxx', quantity: 1 }],
success_url: `${CLIENT_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${CLIENT_URL}/cancel`,
customer_creation: 'always', // 顧客を常に作成
allow_promotion_codes: true, // クーポン入力を許可
automatic_tax: { enabled: true }, // 自動税計算(対象国)
shipping_address_collection: { allowed_countries: ['JP', 'US'] },
phone_number_collection: { enabled: true }
})
テストカード(テストモード)
- 基本:
4242 4242 4242 4242
/ 任意の未来年月 / 任意CVC / 郵便番号任意 - 3Dセキュア例:
4000 0027 6000 3184
セキュリティと運用のポイント
- 秘密鍵はフロントに置かない(必ずサーバでStripe SDKを使用)
- 金額や商品IDはフロント依存にせず、サーバ側で信頼できるソースから解決
- Webhookで支払い確定の最終判断を行い、DB更新やライセンス付与はそこで実行
- 税・送料・通貨はStripe設定と一致させ、金額不整合を防止
- CORSはフロントのオリジンを明示許可、
success_url/cancel_url
はHTTPSで本番ドメインに
まとめ
Stripe Checkoutは、Vue側はAPI呼び出し&リダイレクトだけに集中できるため、実装が極めてシンプルです。バックエンドでセッションを作成し、Webhookで確定処理を行う構成にすれば、拡張やセキュリティにも強い決済基盤になります。まずはテストモードで動作確認し、金額・通貨・税設定を固めたうえで本番化しましょう。