第三話:〇からAIアプリを開発する方法

2024/12/25に公開

第三話:〇からAIアプリを開発する方法

ドンキの外国人行列は半端ないなぁと最近思うこの頃、です。

薬を買うために待っている間、TikTokで見た内容がディープフェイクではないか確認する為、トランプについてChatGPTに尋ねました。

Q:ドナルド・トランプはお酒を飲まないの?

A:ドナルド・トランプはお酒を飲まないそうです。その理由として、彼の兄であるフレッド・トランプ・ジュニアがアルコール依存症で苦しみ、1981年に42歳で亡くなったことが大きな要因とされています。

A:フレッド・トランプ・ジュニアの悲劇的な死が、ドナルド・トランプに「飲酒を避ける」という決意をさせたとされています。トランプ自身もこれについてインタビューで語っており、兄の経験を通じて、アルコールの危険性を強く感じたと述べています。そのため、彼はアルコールだけでなく、喫煙やドラッグの使用も避けてきたとされています。

マジで何でも知っているなぁと思いつつ、この革命的技術を社内Wikiに応用できないかなと思い、このAIチャットボットのアプリ開発シリーズをはじめまたのがきっかけでした。

第一話では、PDFからQ&Aを自動作成し、それをデータベースに取り込む流れを解説しました。
https://note.com/bletainasus/n/nab5840b01b4b

第二話では、そのデータベースから、プロンプトのメッセージをスコアリングし、回答を適切にコントロールする方法についてお伝えしました。
https://www.bletainasus.com/dashboard/post/second-story/

そして今回は、開発したソースを自分自身のホームページ上で販売する方法についてです。
Stripeの記事は数多く存在しますが、世界一シンプルに、説明していきます。

マネー

やはりお仕事である以上、お金はつきものです。

人間のあらゆるすべての活動の根源と言っても過言ではありません。

これをスムーズにインターネット上で得ることができる仕組みを提供してくれるのが、Stripeです。

クレジットカード決済手数料

  • 国内カード:

    決済額の 3.6% + 30円

    (例えば、1,000円の決済では、36円 + 30円 = 66円 が手数料)

  • 海外カード:

    決済額の 4.1% + 30円

    (海外カードには追加の手数料が発生します)

5%くらいは、間を抜かれてしまいますが、それでも9割以上手元に入る可能性があるのであれば、インターネット販売に挑戦しない訳にはいかないですね。

0. ペイメントリンクの作成方法

アカウントがない方は、先にアカウントを作成してください。

https://stripe.com/jp

ダッシュボード画面からスタートしていきます。

https://dashboard.stripe.com/dashboard

テスト環境に変更します。

左のメニューから、「商品カタログ」を押下し、「+商品を作成」を押下します。

各種項目を入力した後、商品を追加ボタンを押下します。

作成後は左のメニューから「Payment Links」を押下し、「+新規」を押下します。

先ほど追加した商品がリストに表示されますので、選択します。

ご必要であれば、各種オプションをチェックし、「リンクを作成」を押下します。

「支払い後」のタブを押下し、リダイレクト先を設定します。

ここが一つポイントで、https://www.xyz.com/dashboard/success?session_id={CHECKOUT_SESSION_ID}のように、session_idを返却するように設定します。

あとは、Payment LinksのリストからURLをコピーするボタンを押下し、ホームページからそのリンクに飛ばせば課金の仕組みは完成です。

1. Stripe購入チェック機能の実装 (pages/api/check-payment-status/route.ts)

支払いイベントを受信するエンドポイントを設定します。

import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
import jwt from 'jsonwebtoken';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
    apiVersion: '2024-12-18.acacia',
});

const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; // Replace with your actual secret key

export async function GET(req: NextRequest) {
    const sessionId = req.nextUrl.searchParams.get('sessionId');
    if (!sessionId) {
        return new NextResponse('Session ID is required', { status: 400 });
    }

    try {
        // Retrieve the session object from Stripe
        const session = await stripe.checkout.sessions.retrieve(sessionId);

        // Check if the payment was successful
        if (session.payment_status === 'paid') {
            // Generate the JWT token with the session details
            const token = jwt.sign(
                {
                    sessionId: session.id,
                    customerId: session.customer,
                    paymentStatus: session.payment_status,
                },
                JWT_SECRET, // Secret key to sign the JWT
                { expiresIn: '5m' } // Token expiration time (1 hour)
            );

            return NextResponse.json({ status: 'succeeded', token });
        } else {
            return NextResponse.json({ status: 'failed' });
        }
    } catch (error) {
        console.error('Error checking payment status:', error);
        return new NextResponse('Error checking payment status', { status: 500 });
    }
}

必要になるライブラリを追加しておいてください。

yarn add stripe jsonwebtoken aws-sdk

2. 支払い完了ページ (success/page.tsx)

支払い完了後にリダイレクトされるページを作成します(こちらはNext13以降のサンプルです。ご入用の際は、逐次置き換えてください)。

'use client';

import { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';

const SuccessPage = () => {
  const searchParams = useSearchParams();
  const [token, setToken] = useState<string | null>(null);
  const [paymentStatus, setPaymentStatus] = useState<string | null>(null);
  const sessionId = searchParams?.get('session_id'); // Retrieve the session ID from the URL

  console.log('Session ID:', sessionId);

  useEffect(() => {
    if (sessionId) {
      // Send a request to your API to check the payment status using the session ID
      fetch(`/api/check-payment-status?sessionId=${sessionId}`)
        .then((response) => response.json())
        .then((data) => {
          if (data.status === 'succeeded') {
            setPaymentStatus('成功');
            setToken(data.token); // Use the token from the response if available
          } else {
            setPaymentStatus('失敗');
          }
        })
        .catch((error) => {
          console.error('Error checking payment status:', error);
          setPaymentStatus('エラー');
        });
    }
  }, [sessionId]);

  const handleGenerateDownloadLink = () => {
    if (token) {
      // Send a request to generate the download link using the token
      fetch('/api/generate-download-link', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ token }),
      })
        .then((response) => response.json())
        .then((data) => {
          if (data.url) {
            // Use 'url' instead of 'downloadLink'
            window.location.href = data.url; // Redirect to the generated download link
          } else {
            alert('ダウンロードリンクの生成に失敗しました');
          }
        })
        .catch((error) => {
          console.error('Error generating download link:', error);
          alert('ダウンロードリンクの生成に失敗しました');
        });
    }
  };

  return (
    <div>
      <h1>支払い結果</h1>
      {paymentStatus ? <p>支払いステータス: {paymentStatus}</p> : <p>支払い状況を確認中...</p>}

      {token && <button onClick={handleGenerateDownloadLink}>ダウンロードリンクを生成</button>}
    </div>
  );
};

export default SuccessPage;

3. ダウンロードリンク生成API (api/generate-download-link/route.ts)

AWS S3の署名付きURLを生成するエンドポイントを作成します。

import { NextRequest, NextResponse } from 'next/server';
import AWS from 'aws-sdk';
import jwt from 'jsonwebtoken';

const s3 = new AWS.S3({
    accessKeyId: process.env.AWS_ACCESS_KEY2 || '',
    secretAccessKey: process.env.AWS_SECRET_KEY2 || '',
    region: process.env.AWS_REGION || '',
});

const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';

export async function POST(req: NextRequest) {
    const { token } = await req.json();  // Expecting token in the body
    if (!token) {
        return NextResponse.json({ error: 'Token is required' }, { status: 400 });
    }

    try {
        // Token verification logic
        const decoded = jwt.verify(token, JWT_SECRET) as { customerId: string; paymentIntentId: string };
        console.log('Token verified:', decoded);

        // S3 signed URL generation
        const params = {
            Bucket: process.env.S3_BUCKET_NAME || '',
            Key: 'test.zip',
            Expires: 60 * 5,  // Expiration in seconds (5 minutes)
        };

        const url = s3.getSignedUrl('getObject', params);
        return NextResponse.json({ url });
    } catch (error) {
        console.error('Error verifying token:', error);
        return NextResponse.json({ error: 'Invalid or expired token' }, { status: 403 });
    }
}

4. 型定義ファイル (必要に応じて types/stripe.d.ts)

Stripeの型が必要であれば、以下のように型定義を追加できます。

declare module 'stripe' {
  export interface CheckoutSession {
    customer_email: string;
  }
}

5.環境変数

.env.local ファイルに以下を設定してください。

STRIPE_SECRET_KEY=sk_test_xxxx

AWS_ACCESS_KEY=your-aws-access-key
AWS_SECRET_KEY=your-aws-secret-key
AWS_REGION=your-aws-region
S3_BUCKET_NAME=your-s3-bucket-name

JWT_SECRET は、アプリケーションのセキュリティを保つために、開発者や運用チームが生成して設定する秘密鍵(シークレットキー)です。このキーは、JSON Web Token (JWT) の署名や検証に使われます。


JWT_SECRET の生成方法

  1. ランダムな値を生成
    • 安全な方法でランダムな文字列を生成します。

    • 例: OpenSSL を使ってランダムな文字列を生成。

      bash
      コードをコピーする
      openssl rand -base64 32
      
      

      例出力: 7wF+bSKgUpQK9aHjTzcyQlW0k+LKTlN2QkDnRkSTVlc=

AWSの環境変数

下記のように、IAMにアクセスして取得します。

ユーザー名をstripeにします。

ユーザーグループs3を作成し、s3フルアクセスのポリシーをアタッチします。

「次へ」を押下し、必要に応じてタグを作成し、「ユーザーの作成」を押下します。

作成されたstripeユーザーの画面にて、「アクセスキーを作成」を押下します。

AWS の外部で実行されるアプリケーションを選択します。

説明タグ値はもし必要であれば設定し、「アクセスキー作成」を押下します。

AWS_ACCESS_KEYとAWS_SECRET_KEYをそれぞれコピーして、環境変数として.envに保存します。

AWS_REGION=ap-northeast-1にします。

任意のS3バケットを作成し、それをS3_BUCKET_NAMEに設定します。

このバケット内にダウンロード対象のファイル(test.zip)を置きます。

これで、支払い後に安全にダウンロードリンクを提供する仕組みの完成です。

yarn build
git add . ; git commit -m "develop stripe" ; git push

テスト

ではペイメントリンクから支払いテストをしてみます。

リダイレクトを受けて、支払いステータスが成功になりました。

ダウンロードボタンを押下すると、test.zipがダウンロードできました。

シンプルにセッションID無しのURLで直接叩いても、支払い状況を確認中…になりますので、ガードは効いていますね。

心配なケースは、セッションIDごと回されて使用されてしまうケースですが、5分間しかURLもトークンも有効ではありませんし、ダウンロードしたファイルを誰かに転送されたら、もうそれは手に追えませんので、このあたりまでのガードが妥当かと思います。

テストはうまくいきましたので、今度はリアルにステップアップさせます。

stripeのPayment Linksメニューに戻り、本番環境にコピーを選択します。

本番環境画面のPayment linksから、URLをコピーして、ご自身のホームページにリンクを貼れば完成です。

全3回に渡って、社内Wiki用のAIアプリを開発し、販売するまでの工程を紹介いたしました。

皆様のマネタイズの参考になりましたら、幸いです。

それではまた、ごきげんよう🍀

Discussion