0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

💡 会員登録→ログイン→購入の3ステップを1つにまとめた話

0
Posted at

はじめに

個人開発で有料サービスを作るとき、こんな悩みはありませんか?

  • 「会員登録画面を作るの面倒だな...」
  • 「せっかく購入ボタンを押してくれたのに、会員登録で離脱されそう」
  • 「認証周りのセキュリティ、ちゃんとできてるか不安」

従来の購入フローは 会員登録 → ログイン → 購入 という3ステップが当たり前でした。でも、これって本当に必要でしょうか?

この記事では、「決済と同時にアカウント作成」 という発想で、ユーザー体験を大幅に改善する設計パターンを紹介します。

この記事で得られること

  • 会員登録画面を作らずに済む設計パターン
  • Stripe Checkoutを活用した具体的なフロー
  • 次回ログインの解決策(マジックリンク)
  • 実装時の注意点とエッジケース

従来のフローの問題点

よくある購入フローを図にすると、こうなります。

従来のフロー(3ステップ)

[購入ボタン] → [会員登録画面] → [ログイン] → [決済画面] → [完了]
                    ↓
                 ここで離脱 😢

😓 何が問題か?

  1. 離脱ポイントが多い

    • 会員登録フォームを見た瞬間に「面倒くさい」と思われる
    • パスワードを考えるのが億劫
  2. 実装コストが高い

    • 会員登録画面
    • メール認証
    • パスワードリセット
    • ログイン画面
    • これ全部作るの?
  3. セキュリティの責任

    • パスワードのハッシュ化、ちゃんとできてる?
    • セッション管理、大丈夫?

発想の転換:「決済 = 会員登録」

ここで逆転の発想です。

「購入する人」は「会員になる人」と同じ ですよね?

だったら、決済のタイミングでアカウントを作ればいい

新しいフロー(1ステップ)

[購入ボタン] → [Stripe決済画面] → [完了・自動ログイン]
                     ↓
            メールアドレスはここで入力
            (Stripeが取得してくれる)

✨ このフローのメリット

項目 従来 新フロー
会員登録画面 必要 不要
パスワード設定 必須 後から任意
離脱ポイント 多い 最小限
実装コスト 高い 低い

具体的なフロー設計

では、具体的にどう実装するか見ていきましょう。

📝 購入フロー(初回)

1. ユーザー:「購入ボタン」を押す
2. サイト:住所入力画面を表示(※任意。サービスで使う場合)
3. サイト:Stripe Checkout セッションを作成
4. ユーザー:Stripe画面でメールアドレス+カード情報を入力
5. Stripe:決済完了 → Webhook でサイトに通知
6. サイト(Webhook処理):
   - メールアドレスでユーザーを検索
   - いなければ新規作成
   - セッション発行
7. ユーザー:完了画面(ログイン済み状態)

🔧 実装のポイント

1. Stripe Checkoutでメールアドレスを取得

Stripe Checkoutを使うと、決済画面でメールアドレスを入力させることができます。このメールアドレスがそのまま「ユーザーID」になります。

checkout.ts
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  payment_method_types: ['card'],
  line_items: [
    {
      price: 'price_xxxxx', // Stripeで作成した価格ID
      quantity: 1,
    },
  ],
  success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://example.com/cancel',
  // ↓ これが重要!メールアドレスを取得できる
  customer_email: undefined, // undefinedにすると、ユーザーに入力させる
});

2. Webhookでユーザー自動作成

決済完了時にStripeからWebhookが飛んできます。ここでユーザーを作成します。

webhook.ts
// Stripe Webhook: checkout.session.completed
async function handleCheckoutComplete(session: Stripe.Checkout.Session) {
  const email = session.customer_details?.email;

  if (!email) {
    throw new Error('Email not found');
  }

  // 既存ユーザーを検索
  let user = await db.selectFrom('users')
    .where('email', '=', email)
    .selectAll()
    .executeTakeFirst();

  // いなければ新規作成
  if (!user) {
    const result = await db.insertInto('users')
      .values({
        email,
        plan: 1, // プレミアムプラン
        plan_purchased_at: new Date().toISOString(),
      })
      .returning(['id', 'email'])
      .executeTakeFirst();

    user = result;
  } else {
    // 既存ユーザーならプランをアップグレード
    await db.updateTable('users')
      .set({
        plan: 1,
        plan_purchased_at: new Date().toISOString(),
      })
      .where('id', '=', user.id)
      .execute();
  }

  // 購入履歴を保存
  await db.insertInto('purchases')
    .values({
      user_id: user.id,
      stripe_session_id: session.id,
      amount: session.amount_total,
      status: 'completed',
    })
    .execute();

  return user;
}

3. 完了画面でセッション発行

Webhookの処理が終わったら、完了画面でユーザーをログイン状態にします。

success.ts
// /success?session_id=xxx のページ
export async function loader({ request }: LoaderFunctionArgs) {
  const url = new URL(request.url);
  const sessionId = url.searchParams.get('session_id');

  // Stripeからセッション情報を取得
  const session = await stripe.checkout.sessions.retrieve(sessionId);
  const email = session.customer_details?.email;

  // ユーザーを取得してセッション発行
  const user = await db.selectFrom('users')
    .where('email', '=', email)
    .selectAll()
    .executeTakeFirst();

  // Cookieにセッショントークンをセット
  return redirect('/dashboard', {
    headers: {
      'Set-Cookie': await createSession(user.id),
    },
  });
}

次回ログインはどうする?

ここで疑問が出てきます。

「パスワードを設定してないけど、次回どうやってログインするの?」

🔐 解決策:マジックリンク

マジックリンク とは、メールに送られたURLをクリックするだけでログインできる仕組みです。

次回ログインフロー

1. ユーザー:「ログイン」ボタンを押す
2. サイト:メールアドレス入力画面を表示
3. ユーザー:メールアドレスを入力
4. サイト:ログイン用URLをメール送信
5. ユーザー:メールのURLをクリック
6. サイト:トークン検証 → ログイン完了

パスワードは後から設定可能に

マジックリンクだけだと毎回メール確認が必要で面倒、という人もいます。そこで、パスワードは後から任意で設定できる ようにします。

マイページ
├── プロフィール編集
└── パスワード設定 ← これを用意
    └── 「パスワードを設定すると、次回からメール+パスワードでログインできます」

エッジケースへの対応

実装時に考慮すべきケースをまとめます。

ケース1: 同じメールで再度購入しようとした

// Stripe Checkout前にチェック
const existingUser = await db.selectFrom('users')
  .where('email', '=', inputEmail)
  .selectAll()
  .executeTakeFirst();

if (existingUser && existingUser.plan === 1) {
  // すでにプレミアム会員
  return json({ error: 'すでにプレミアム会員です。ログインしてください。' });
}

ケース2: 購入済みユーザーが未ログインで購入ボタンを押した

これはWebhookで吸収できます。既存ユーザーならプラン情報を更新するだけ。

ケース3: パスワード忘れ(設定済みの場合)

マジックリンクでログイン → マイページでパスワード再設定、という導線を用意。


DB設計(参考)

-- ユーザーテーブル
CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT UNIQUE NOT NULL,
  password_hash TEXT,  -- NULL許容(後から設定)
  plan INTEGER DEFAULT 0,  -- 0:無料, 1:プレミアム
  plan_purchased_at TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

-- 購入履歴
CREATE TABLE purchases (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  stripe_session_id TEXT NOT NULL,
  amount INTEGER,
  status TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

-- マジックリンク
CREATE TABLE magic_links (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  token TEXT UNIQUE NOT NULL,
  expires_at TEXT NOT NULL,
  used_at TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

まとめ

この記事では、「決済と同時にアカウント作成」 というパターンを紹介しました。

✅ ポイントおさらい

  • 会員登録画面は不要 - Stripe Checkoutでメールアドレスを取得
  • パスワードも不要 - 後から任意で設定可能に
  • 離脱ポイント最小化 - 購入ボタン → 決済 → 完了 の1本道
  • 次回ログイン - マジックリンクで解決
  • セキュリティ - 認証の複雑な部分はClerkやSupabaseに任せるのも手

💭 最後に

個人開発では「作らなくていいものは作らない」が鉄則です。

会員登録画面、ログイン画面、パスワードリセット...これらを全部自前で作る必要は本当にあるでしょうか?

「買ったらすぐ使える」 というシンプルな体験が、ユーザーにとっても開発者にとっても幸せな選択かもしれません。


最後まで読んでいただきありがとうございました!
質問やフィードバックがあれば、ぜひコメントでお聞かせください 🙌

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?