【Vue.js】Stripe決済を組み込む方法|Checkout API活用例

【Vue.js】Stripe決済を組み込む方法|Checkout API活用例 Vue.js

最短で安全にカード決済を導入するなら、Stripeのホスト型UI「Checkout」が便利です。バックエンドでセッションを作成し、フロント(Vue)からStripe.jsでリダイレクトするだけで決済画面を提供できます。この記事では、Vue 3 + Node/Express を例に、Checkout セッション作成 → 決済ページへ遷移 → Webhookで支払い確定を受け取るまでの実装手順をまとめます。

全体像

  1. バックエンド:/create-checkout-session を実装(秘密鍵使用)
  2. フロント(Vue):ボタン押下で上記APIを叩き、Stripe Checkoutへリダイレクト
  3. 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で確定処理を行う構成にすれば、拡張やセキュリティにも強い決済基盤になります。まずはテストモードで動作確認し、金額・通貨・税設定を固めたうえで本番化しましょう。