1
1

StripeとFastAPIとvue.jsによる決済できるウェブアプリの実装。テスト環境で動かす

Last updated at Posted at 2024-08-26

この記事はStripeによって決済するウェブアプリをゼロから作るサンプルを説明します。フロントエンドはTypeScriptのvue.jsで、バックエンドはPythonのFastAPIで書きます。

vue.jsとFastAPIを使っている読者の場合はすぐここに載っている手順で実行できるはずですが、そうでない場合でもStripeを実装する大体の流れの参考になれるでしょう。

ここでやることと目標

Stripeとは

Stripe(ストライプ)とはウェブアプリ上で決済を実装するためのサービスです。ネットでお金のやり取りをする時によく使われて、ウェブエンジニアの間で結構人気です。

Stripe決済をウェブサイトに入れるには普段フロントエンド側もバックエンド側もコードを書く必要があって、かなり複雑でわかりにくくて挫折してしまいそうです。ただStripeのいいところはテスト環境で開発できることだと思います。

テスト環境

お金に関わることなので、開発の時に気軽に本番のクレジットカードや決済の情報を使うわけにはいきませんよね。だから普段はテスト環境で本番に真似する擬似的な支払いをすることができると開発しやすいです。

それに本番環境を使うには自分や会社の決済情報を入力する必要があって、試してみたくてもすぐにはできないが、テスト環境はただStripeのアカウントを登録したらすぐ使えます。だからStripeを使うかどうか迷っている人もまず簡単に試して上手くいくかどうか確認することができます。

作ってみるウェブアプリ

今回の記事では全体の流れを勉強するために、テスト環境で動く簡単なウェブアプリを実装するコードを載せて解説します。

支払い方法としては、1回ごと支払ったら終了するものと、クレジットカード登録したら定期的に払い続けることになる「サブスクリプション」(subscription)、長いのでここでは略して「サブスク」、という方法に大きく分けられています。やり方は少し違うので、両方の使い方を勉強するために今回はどっちも実装することにします。

纏めてみるとこんな感じのウェブアプリにします。

  • ユーザーログインと新規登録の画面がある
  • 登録するにはクレジットカード情報も入れてサブスクする必要がある
  • 登録した後、ガチャを回すメイン画面に入る
  • ガチャを回すのにまず課金する必要がある
  • サブスクと支払いの情報はStripeに登録されるから再度ログインする時に確認できる

このように、支払いしかないウェブアプリです。本番なら中身が何かいいサービスがあるはずですが、今回は支払いの勉強するためだけなので支払いに関する部分しか殆ど触れません。だからこんな無駄なウェブアプリになります。

それに、ユーザー登録は普段Stripeとは別としてバックエンドで作成しておいたデータベースやAWSのCognitoなどのサービスを使った方がいいのですが、今回はStripe重視なので、全部省略してユーザー情報の記録もStripeだけ済ませることにします。パスワードも使わずに電子メールだけでユーザー登録と認証を行います。

又、ウェブデザインとかも重視しません。クレジットカード入力とユーザー情報の入力と表示が正しければそれでいいです。簡単にわかりやすい地味な画面にします。

フロントエンドもバックエンドも、一般に使っている言語とフレームワークはどれも実装できますが、今回は私がいつも使っている馴染みのFastAPI(バック)とvue.js(フロント)を採用します。

以前もこの組み合わせで書いた簡単なウェブアプリのサンプルを載せた記事があったので、これも読んでいただけたらと思います。

又、vue.jsだけで書いたCognito実装の記事もあります。CognitoもよくStripeと組み合わせられるので、興味があればこの記事も一緒に読んだいただけたらと思います。

開発の大体の流れ

次はこのような順番で解説していきます。

  • 使うパソコンの環境設定を整えておく
  • Stripeのウェブサイトにアカウント登録する
  • テスト用の「公開可能キー」と「シークレットキー」を取得する
  • 「サブスク」に使う「商品」と「プラン」を登録する
  • バックエンドでStripeのサブスク登録のためのコードを書く
  • フロントエンドでクレジットカード情報を登録する画面を作る
  • 上手く動くかテストして確認する
  • 問題がなれば次は本番の実装(この記事ではそこまで説明しません

環境設定

pythonライブラリーのインストール

バックエンドの方はFastAPIを使うのでまずはFastAPIと関連のライブラリーをインストールします。

pip install fastapi uvicorn python-multipart

Stripeを使うためにPythonではstripeというライブラリーがあります。これもインストールする必要があります。

pip install stripe

vueプロジェクト作成

次はプロジェクトの作成です。vuetifyを使うことにします。yarnで実行します。

yarn create vuetify
yarn create v1.22.22
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-vuetify@2.2.6" with binaries:
      - create-vuetify
[##########] 10/10
Vuetify.js - Material Component Framework for Vue

✔ Project name: … fastrivue
✔ Which preset would you like to install? › Barebones (Only Vue & Vuetify)
✔ Use TypeScript? … No / Yes
✔ Would you like to install dependencies with yarn, npm, pnpm, or bun? › yarn
✔ Install Dependencies? … No / Yes

◌ Generating scaffold...
◌ Installing dependencies with yarn...

yarn install v1.22.22
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 16.99s.

fastrivue has been generated at /Users/phyblas/Library/CloudStorage/Dropbox/web/fastrivue

Discord community: https://community.vuetifyjs.com
Github: https://github.com/vuetifyjs/vuetify
Support Vuetify: https://github.com/sponsors/johnleider
✨  Done in 100.72s.

作成の途中で出てくる選択肢はこのように選びます。

✔ Barebones (Only Vue & Vuetify)
✔ Yes
✔ yarn
✔ Yes

プロジェクト名は「fastrivue」にします。fastapi+stripe+vueを合わせた適当な名前です。

作成したプロジェクトフォルダに入って必要なパッケージを追加します。

cd fastrivue

今回追加する必要があるのは2つだけ。まず必要なのはバックエンド側に接続するためのaxiosです。

yarn add axios
yarn add v1.22.22
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 9 new dependencies.
info Direct dependencies
└─ axios@1.7.5
info All dependencies
├─ asynckit@0.4.0
├─ axios@1.7.5
├─ combined-stream@1.0.8
├─ delayed-stream@1.0.0
├─ follow-redirects@1.15.6
├─ form-data@4.0.0
├─ mime-db@1.52.0
├─ mime-types@2.1.35
└─ proxy-from-env@1.1.0
✨  Done in 1.21s.

もう一つは今回の主役である、stripeに接続するパッケージです。

yarn add @stripe/stripe-js
yarn add v1.22.22
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ @stripe/stripe-js@4.3.0
info All dependencies
└─ @stripe/stripe-js@4.3.0
✨  Done in 2.16s.

なお、紛らわしいので一応説明しておきますが、これは間違いです。

yarn add stripe

これだと別のパッケージになります。このパッケージはバックエンドとしてJavaScript(又はTypeScript)を使うためのパッケージです。

混同しやすいが、今回はフロントエンドとして使うだけで、バックエンドはPythonのFastAPIを使うので、このパッケージは要りません。

全体の構造

作成したばかりのfastrivueプロジェクトフォルダ内は最初から色んなファイルが入っているはずですが、特に変更する必要がありません。

書き直すのは「src/App.vue」だけで、その他にこれらを書いて追加します。

  • web.py
  • src/components/login.vue
  • src/components/subsc.vue
  • src/components/gacha.vue

「web.py」はバックエンドの実装のコードです。今回は纏めて一つだけのファイルにします。

src/componentsの中はそれぞれのウェブアプリを織り成す部品です。

そうしたらこのような構造になります。

├──fastrivue
│   ├── web.py
│   ├── src
│   │   ├── App.vue
│   │   ├── components
│   │   │   ├── login.vue
│   │   │   ├── subsc.vue
│   │   │   └── gacha.vue
│   │   └── (その他)
│   ├── dist
│   └── (その他)

実際に書き終わった後のフォルダ。

qsfv14.png

テスト環境で始める

Stripeアカウント登録

まずはstripeのウェブサイトに入って新規アカウント登録を行います。

登録した後自分の決済などの情報を入力してと言われますが、今すぐする必要がないのです。テスト環境で始めたい場合はこれを省略してダッシュボードの画面に入ることができます。そうしたら「テスト環境」という設定になっています。

ダッシュボードのメイン画面の右側に「公開可能キー」と「シークレットキー」が搭載されています。これをコピーしてすぐ使えるようになります。

qsfv01.png

公開可能キーフロントエンド側に使うコードです。「pk_」から始めます。

シークレットキー又はAPIキーバックエンド側に使うコードです。「sk_」から始めます。

なんで2つも必要かというと、恐らく秘密かどうかの違いでしょう。シークレットキーが必要となるのは決済情報を更新したり削除したりする操作なので、フロントエンド側に流して晒されることは許されないでしょう。それに対し公開可能キーが必要とするフロントエンド側の操作はクレジットカード登録など顧客本人に関わる機能に限られています。だからこういう使い分けをしているでしょう。

商品とプランの作成

サブスクを行うためにはまず商品と支払いのプランを定義しておく必要があります。これはStripeのダッシュボードで行うこともできますが、APIを通じて操作することもできます。

バックエンドを書く時もこのAPIを使って顧客やサブスクを作成することになるので、最初から商品とプランもAPIで作成しておいた方がしっくり来ると思うので、今回はAPIで行います。

まず商品の定義はPythonでこのように書いて実行します。(ここにメモしておいたAPIキーを入れる必要があります)

import stripe

stripe.api_key = 'sk_test_*****'
product = stripe.Product.create(name='あぷり')
print(product)

これで商品が作成されて、このような情報が見られます。

{
  "active": true,
  "attributes": [],
  "created": 1724675940,
  "default_price": null,
  "description": null,
  "id": "prod_QjVCnjOMg5q4QD",
  "images": [],
  "livemode": false,
  "marketing_features": [],
  "metadata": {},
  "name": "\u3042\u3077\u308a",
  "object": "product",
  "package_dimensions": null,
  "shippable": null,
  "statement_descriptor": null,
  "tax_code": null,
  "type": "service",
  "unit_label": null,
  "updated": 1724675940,
  "url": null
}

商品を定義する時に色んな情報を入れることができますが、実際に必要なのは「名前」(name)だけです。

「prod_」から始める商品のIDは自動的に発行されて、これは次に使う必要があるので、控えておきましょう。

APIで作成した後、ダッシュボードの方で見たらその商品は「商品カタログ」に表示されることになっています。これでちゃんと繋がっているとわかりますね。

qsfv02.png

ただしまだ値段設定はしていないので、今は「価格がありません」と表示されていますね。

次はプランの定義です。これもAPIを通じて行えます。

import stripe

stripe.api_key = 'sk_test_*****'
plan = stripe.Plan.create(
    product='prod_QjVCnjOMg5q4QD',
    amount=1024,
    currency='jpy',
    interval='month',
    nickname='てきとうぷらん'
)
print(plan)

実行の結果。

{
  "active": true,
  "aggregate_usage": null,
  "amount": 1024,
  "amount_decimal": "1024",
  "billing_scheme": "per_unit",
  "created": 1724676082,
  "currency": "jpy",
  "id": "plan_QjVFDfaACOyJJY",
  "interval": "month",
  "interval_count": 1,
  "livemode": false,
  "metadata": {},
  "meter": null,
  "nickname": "\u3066\u304d\u3068\u3046\u3077\u3089\u3093",
  "object": "plan",
  "product": "prod_QjVCnjOMg5q4QD",
  "tiers_mode": null,
  "transform_usage": null,
  "trial_period_days": null,
  "usage_type": "licensed"
}

そうしたらもう一度商品カタログを見たら、入れた通りの価格は表示されます。

qsfv03.png

クリックして商品の詳細を読んでみたら、作成したプランの情報も入っています。

qsfv04.png

プランの定義に商品のidと支払い方式などの情報が必要です。productは先程作成した商品のidで、amountは支払う料金で、currencyは金貨の種類で、intervaldayweekmonthyearなどどれくらいの間隔で支払いするを決めるのです。nicknameはなくてもいいのでここでは一応適当につけました。

尚、商品とプランを予め定義する必要があるのはサブスクで支払う場合だけです。一回きり支払いの場合は特に必要ありません。

バックエンドでの実装

アカウント作成と商品とプランの定義が終わったら次は早速実装です。まずはバックエンドの方。これはPythonフレームワークであるFastAPIで書きます。そこまで長くはないので、一つだけのファイルに纏めます。

web.py
from fastapi import FastAPI,Request,HTTPException
from fastapi.staticfiles import StaticFiles
import stripe

# Stripeのウェブサイトから取得できたシークレットキー
stripe.api_key = 'sk_test_*****'
# 予め定義しておいた商品のプランのID
plan_id = 'plan_QjVFDfaACOyJJY'

app = FastAPI()

# サブスクを行うAPI
@app.post('/subsc_okonau')
async def subsc_okonau(request: Request):
    try:
        req = await request.json() # ウェブサイトから送られたユーザーとクレジットカードの情報
        email = req['email']
        name = req['name']
        pm_id = req['pm_id'] # 支払い方法、つまりクレジットカードのID
        
        # この電子メールが既に登録されているかどうかの確認
        customer_query = stripe.Customer.search(query=f'email: "{email}"')
        if(customer_query['data']):
            raise HTTPException(status_code=403,detail='この電子メールは既に使われているのだ。')
        
        # 顧客作成
        customer = stripe.Customer.create(
            email=email,
            name=name
        )
        
        # 支払い方法を添付する
        try:
            pm = stripe.PaymentMethod.attach(
                payment_method=pm_id,
                customer=customer.id
            )
        except Exception as err:
            # カードに問題があって添付できない場合はここでエラーが出る
            stripe.Customer.delete(customer.id)
            raise HTTPException(status_code=403,detail=str(err))
        
        # この顧客と支払い方法でサブスクを作成する
        subsc = stripe.Subscription.create(
            customer=customer.id,
            items=[{'plan': plan_id, 'quantity': 1}],
            default_payment_method=pm_id
        )
        
        # サブスクが成功かどうかを確認。失敗したらエラーを出して終了
        if(subsc['status']!='active'):
            stripe.Customer.delete(customer.id)
            raise HTTPException(status_code=403,detail='何か間違いが発生してサブスクは完成できないらしいのだ。')
        
        # サブスクが成功した後、サブスクのIDも顧客情報のメタデータの中に入れて更新する
        customer = stripe.Customer.modify(
            customer.id,
            invoice_settings={'default_payment_method': pm_id},
            metadata={'kakin': 0, 'subsc_id': subsc.id},
        )
        
        # 顧客情報をブラウザの方へ返す
        return customer
    except Exception as err:
        raise HTTPException(status_code=500,detail='エラー:'+str(err))

# ログインするAPI
@app.post('/login_suru')
async def login_suru(request: Request):
    try:
        req = await request.json()
        email = req['email']
        # この電子メールを使っている顧客が登録されているかどうか確認
        customer_query = stripe.Customer.search(query=f'email: "{email}"')
        if(not customer_query['data']):
            raise HTTPException(status_code=403,detail='この電子メールはまだ登録されていないのだ。')
        
        # 顧客情報を取得する
        customer = customer_query['data'][0]
        # サブスク情報を取得する
        subscr = stripe.Subscription.retrieve(customer['metadata']['subsc_id'])
        # サブスクはまだ有効かどうか確認
        if(subscr['status']!='active'):
            stripe.Customer.delete(customer.id)
            raise HTTPException(status_code=403,detail='サブスクはもう無効になっているので、もう一度サブスクするのだ。')
        
        # 問題なければログイン成功。顧客情報をブラウザ側へ返す
        return customer
    except Exception as err:
        raise HTTPException(status_code=500,detail='エラー:'+str(err))

# 課金を行うAPI
@app.post('/kakin_okonau')
async def kakin_okonau(request: Request):
    try:
        req = await request.json()
        ryoukin = int(req['ryoukin']) # 課金する料金
        cid = req['cid'] # 顧客のID
        # 顧客情報取得
        customer = stripe.Customer.retrieve(cid)
        # 一度きりの支払いをする。支払い方法はサブスクの時に登録したのと同じものを流用する
        payment_intent = stripe.PaymentIntent.create(
            amount=ryoukin,
            currency='jpy',
            payment_method=customer.invoice_settings['default_payment_method'],
            customer=cid,
            confirm=True,
            automatic_payment_methods={'enabled': True, 'allow_redirects': 'never'}
        )
        # 支払いがちゃんと成功かどうか確認
        if(payment_intent['status']!='succeeded'):
            raise HTTPException(status_code=403,detail='何か間違いが発生して課金は完成できないらしいのだ。')
        
        # 支払いが成功したから、メタデータの中にある課金の数値も更新する
        customer['metadata']['kakin'] = int(customer['metadata']['kakin']) + ryoukin
        customer = stripe.Customer.modify(cid,metadata=customer['metadata'])
        return customer
    except Exception as err:
        raise HTTPException(status_code=500,detail='エラー:'+str(err))

# ガチャを回す、つまり消費するだけのAPI
@app.post('/gacha_mawasu')
async def gacha_mawasu(request: Request):
    try:
        req = await request.json()
        cid = req['cid']
        customer = stripe.Customer.retrieve(cid)
        # 課金の数値から引くだけ
        kakin = int(customer['metadata']['kakin']) - 300
        if(kakin<0): # 課金したお金が足りなければエラー
            raise HTTPException(status_code=403,detail='お金は足りないのだ。')
        
        # メタデータの中の課金を更新する
        customer['metadata']['kakin'] = kakin
        customer = stripe.Customer.modify(cid,metadata=customer['metadata'])
        return customer
    except Exception as err:
        raise HTTPException(status_code=500,detail='エラー:'+str(err))

# 退会、つまりユーザーを消すAPI
@app.post('/taikai_suru')
async def taikai_suru(request: Request):
    try:
        req = await request.json()
        cid = req['cid']
        return stripe.Customer.delete(cid)
    except Exception as err:
        raise HTTPException(status_code=500,detail='エラー:'+str(err))
        
# 静的ファイルの定義。vue.jsによってプリコンパイルされる
app.mount('/', StaticFiles(directory='dist',html=True))

バックエンドのコードは主にStripeに接続するためのAPIだけです。Stripeとの接続は、クレジットカード情報登録以外全部バックエンドでやっています。

クレジットカード情報の処理だけがフロントエンド側で行われるという仕組みは、「顧客のクレジットカード情報がサーバーに流れないように」配慮されているためでしょう。

サーバーで処理される「支払い方法登録」はクレジットカード情報ではなく、フロントエンド側から登録された「支払い方法のID」だけとなっています。

「顧客登録」と「支払い方法登録」と「サブスク登録」は本来別々やってもいいですが、今回は登録したらすぐサブスクさせるということにしています。だから長い一連の処理になりますが、わかりやすいように各段階の説明を記述しています。

今回はサブスクがメインではありますが、1回きりの支払い、つまり課金の処理も入れています。サブスクはSubscriptionクラスで行われるのに対し、課金はPaymentIntentクラスで行われます。今回ではサブスクしてから課金できるというようにしているので、課金の時は支払い情報の準備をする必要がなく、ただサブスクの時に使ったデフォルト支払い方法を流用するという形にしています。

古い記事を読んだら支払いにChargeというクラスが使われることもあります。ChargePaymentIntentと大体似て、同じように支払いに使うこともできますが、PaymentIntentの方が新しく搭載された機能で、現在Chargeの代わりにPaymentIntentを使った方がいいと言われているので、今使い始める人にとってChargeの存在は無視してもいいです。

バックエンド側のコードだけ読んでも具体的にどういうやり取りをするか想像しにくくて漠然とするでしょう。では次にフロントエンド側の実装に入ります。

フロントエンドでの実装

フロントエンドの方はTypeScriptのvue.jsで書いて、4つの.vueファイルに分けられます。

App.vue

メインのファイルです。ここから3つのコンポネントが呼び出されます。ログイン処理の関数はここに定義しています。

<template>
  <v-app>
    <v-main>
      <v-container>
        <v-card class="text-h5 px-2 py-2 mx-1 my-1">
          ただのつまらないウェブアプリ
        </v-card>
        <login v-if="page == 'ログイン'" @login_suru="login_suru" @subsc="page = 'サブスク'" />
        <subsc v-if="page == 'サブスク'" @login_shita="page = 'ガチャ'" />
        <gacha v-if="page == 'ガチャ'" @logout_suru="logout_suru" />
      </v-container>
    </v-main>
  </v-app>
</template>

<script lang="ts">
import axios from "axios";
const root_url: string = "http://127.0.0.1:8000";

export default {
  data() {
    return {
      page: "" // 表示してるページ
    };
  },
  methods: {
    // APIに接続してログインを行う
    login_suru(email: string) {
      axios.post(root_url + "/login_suru", { email })
        .then((res) => {
          sessionStorage.setItem("customer", JSON.stringify(res.data));
          this.page = "ガチャ";
        })
        .catch((err) => {
          alert(err.response.data.detail);
          this.page = "ログイン";
        });
    },
    // ログアウトする処理
    logout_suru() {
      sessionStorage.clear();
      this.page = "ログイン";
    }
  },
  mounted() {
    // アクセスした時にセッションストレージの中のログイン状態を調べる
    let customer = sessionStorage.getItem("customer");
    if (customer && customer != "null") {
      // 前回からログインしているならログイン状態にする
      this.login_suru(JSON.parse(customer).email);
    }
    else {
      // 既にログイン状態でなければログイン画面へ
      this.page = "ログイン";
    }
  }
};
</script>

components/login.vue

ログイン画面を作るためのコンポネントです。実際にログイン処理の定義はApp.vueの方に書いてあるからこの部分は短めです。

<template>
  <v-card class="px-2 py-2 mx-1 my-1">
    <div>電子メールを入力してログインするのだ。</div>
    <v-text-field class="px-2 py-1" v-model="email" label="電子メール" hide-details />
    <div class="d-flex align-center mx-2 my-2">
      <v-btn class="mx-2" @click="$emit('login_suru', email)" color="primary">
        ログインする
      </v-btn>
      <v-btn @click="$emit('subsc')" color="secondary">まだ登録していない方はこちら</v-btn>
    </div>
  </v-card>
</template>

<script lang="ts">
export default {
  data() {
    return {
      email: "",
    };
  },
};
</script>

components/subsc.vue

顧客登録からサブスクまでの一連の処理を行う画面を構成するコンポネントです。今回で中心とも言える部分です。ここでクレジットカードの情報を入力してStripeに接続して支払い方法を登録してからサブスクを行います。

<template>
  <v-card class="px-2 py-2 mx-1 my-1">
    <div>電子メールと名前とクレジットカード情報を入力して登録するのだ。</div>
    <v-text-field class="px-2 py-1" v-model="email" label="電子メール" hide-details />
    <v-text-field class="px-2 py-1" v-model="name" label="名前" hide-details />
    <div class="px-2 py-3 mx-2 my-3" id="card-element"></div>
    <v-btn class="mx-2" @click="subsc_okonau" color="primary">登録する</v-btn>
  </v-card>
</template>

<script lang="ts">
import axios from "axios";
import { loadStripe, Stripe, StripeCardElement } from '@stripe/stripe-js';
// Stripeのダッシュボードから取得しておいた公開可能キー
const pk: string = "pk_test_*****"
const root_url: string = "http://127.0.0.1:8000";

export default {
  data() {
    return {
      stripe: null as null | Stripe,
      card: null as null | StripeCardElement,
      email: "",
      name: "",
    };
  },
  computed: {

  },
  methods: {
    // クレジットカード情報を入力する欄を初期化する
    async start_stripe() {
      this.stripe = await loadStripe(pk);
      if (this.stripe) {
        this.card = this.stripe.elements().create("card");
        this.card.mount("#card-element");
      }
      else {
        alert("Stripeの初期化は失敗!");
      }
    },
    // サブスクを行う
    subsc_okonau() {
      if (!this.email) {
        alert("まだ電子メールを入れていないのだ");
        return;
      }
      if (!this.name) {
        alert("まだ名前を入れていないのだ");
        return;
      }
      if (this.stripe && this.card) {
        // 入力したカードを使って支払い方法を定義する
        this.stripe.createPaymentMethod({ type: "card", card: this.card })
          .then(res => {
            if (!res.error) { // エラーがないか確認
              let data = {
                pm_id: res.paymentMethod.id, // 支払い方法のID
                email: this.email,
                name: this.name,
              }
              // 支払い方法の作成にエラーがなければAPIに接続してサブスクを行う
              axios.post(root_url + "/subsc_okonau", data)
                .then((res) => {
                  alert("サブスク完了");
                  // サブスクが成功したらセッションストレージに保存してログインする
                  sessionStorage.setItem("customer", JSON.stringify(res.data));
                  this.$emit("login_shita");
                })
                .catch((err) => {
                  alert(err.response.data.detail);
                });
            }
            else { // クレジットカード情報の間違いなどによってエラーが出る場合
              alert(res.error.message);
            }
          });
      }
    },
  },
  mounted() {
    // 初めてこの画面に入った時まずクレジットカード情報入力の欄を初期化する
    this.start_stripe();
  }
};
</script>

<style scoped>
.StripeElement {
  border: 2px solid #44aa33;
  background-color: #eefff6;
  border-radius: 5px;
}
</style>

components/gacha.vue

ログインした後現れる画面です。本来この部分は何かをするアプリのメイン部分になるばすですが、今回では特に何も実装していません。ただ課金とガチャごっこをするだけにしています。

<template>
  <v-card v-if="customer" class="px-2 py-2 mx-1 my-1">
    <div class="mx-2 my-1">名前:{{ customer.name }}</div>
    <div class="mx-2 my-1">電子メール:{{ customer.email }}</div>
    <div class="mx-2 my-1">課金:{{ customer.metadata.kakin }}</div>
    <v-text-field class="px-2 py-1" v-model="ryoukin" label="課金したい料金" hide-details />
    <v-btn class="mx-2" @click="kakin_okonau" color="primary">課金する</v-btn>
    <v-btn class="mx-2" @click="gacha_mawasu" color="warning">ガチャする</v-btn>
    <v-btn class="mx-2" @click="$emit('logout_suru')" color="secondary">ログアウトする</v-btn>
    <v-btn class="mx-2" @click="taikai_suru" color="red">退会する</v-btn>
  </v-card>
</template>

<script lang="ts">
import axios from "axios";
const root_url: string = "http://127.0.0.1:8000";

export default {
  data() {
    return {
      // 顧客の情報
      customer: { id: "", name: "", email: "", metadata: { kakin: 0 } },
      ryoukin: 50, // 支払う料金の欄
    };
  },
  methods: {
    // APIに接続して課金処理を行う
    kakin_okonau() {
      let data = {
        cid: this.customer.id,
        ryoukin: this.ryoukin
      }
      axios.post(root_url + "/kakin_okonau", data)
        .then((res) => {
          alert(this.ryoukin + "円課金完了");
          sessionStorage.setItem("customer", JSON.stringify(res.data));
          this.customer = res.data;
        })
        .catch((err) => {
          alert(err.response.data.detail);
        });

    },
    // ガチャを回す(課金を減らすだけ)
    gacha_mawasu() {
      axios.post(root_url + "/gacha_mawasu", { cid: this.customer.id })
        .then((res) => {
          console.log(res);
          alert("ガチャガチャガチャ!");
          sessionStorage.setItem("customer", JSON.stringify(res.data));
          this.customer = res.data;
        })
        .catch((err) => {
          alert(err.response.data.detail);
        });
    },
    // 顧客を賛助する
    taikai_suru() {
      axios.post(root_url + "/taikai_suru", { cid: this.customer.id })
        .then((res) => {
          console.log(res);
          alert("退会したのだ!");
          this.$emit("logout_suru");
        })
        .catch((err) => {
          alert(err.response.data.detail);
        });
    },
  },
  mounted() {
    // セッションストレージの中の顧客情報を取得する
    let customer = sessionStorage.getItem("customer");
    if (customer && customer != "null") {
      Object.assign(this.customer, JSON.parse(customer));
    }
  }
};
</script>

実行

起動する

全部コードを書き終わったら実行して動きを確認しましょう。

まずフロントエンドの方はプリコンパイルを実行します。

yarn run build

問題がなければこれで「dist」フォルダにhtmlとJavaScriptが作成されて、これをFastAPIの静的ファイルとして扱われます。

次はバックエンドのサーバーを実行します。

uvicorn web:app --reload

これでhttp://127.0.0.1:8000のURLでブラウザからアクセスできます。

アクセスしてみる

アクセスしたらこのようなログイン画面になります。

qsfv05.png

まず登録するための画面に入ります。

qsfv06.png

カードの情報に関しては今テストなのでStripe側から準備しておいたダミーのカード番号を入れて擬似的に支払うことができます。

使えるカード番号はここに書いてあります。

今回は日本のVISAで支払うという設定にするので「4000003920000003」を入れます。有効期間は未来の月と年を入れたらいいです。CVCは何を入れても通れます。

qsfv07.png
(ここで入力した電子メールはただ適当で、実在しません)

因みに、残高不足で支払い拒否されるという設定などのカード番号も準備されています。例えば「4000000000009995」を入れたらそれらしいエラーが実際に出てきます。これで本番と同じような仕様を確認することができますね。

qsfv08.png

問題ないクレジットカードの情報を入れたらサブスクが完成して、自動的にログインして、この画面に入ります。ただ顧客の情報の表示と少し簡単な動作のボタンだけ準備されています。

qsfv09.png

料金を入力して「課金する」を押したら課金が実行されて、「課金:」の右の数値は増えます。

qsfv10.png

尚、ここまで来てもしダッシュボードの「顧客」を調べてみたらちゃんと登録されているとわかります。

qsfv11.png

そしてクリックして中を見たらサブスクと支払いの歴史も入っているはずです。

qsfv12.png

最後に「ガチャする」を押したらただメタデータの中の課金のお金が300円減るだけで、これは支払いとは関係ない処理ですが、一応Stripeに記録されている情報を利用してこのようなこともできます。

qsfv13.png

こんな感じで、こうやって支払いを実行できるウェブサイトを作ることができます。

ただし今のはあくまでテスト環境で擬似的な支払いなので、本番を使うためにはちゃんとした決済情報をStripeのウェブサイトに登録して本番用の「公開可能キー」と「シークレットキー」を取得する必要があります。

APIの纏め

APIの実装で色んなクラスのオブジェクトが関わっているのでここではちょっと登場したものの纏めをしておきます。

クラス 意味 IDの接頭辞
Product 商品 prod_
Plan プラン plan_
Customer 顧客 cus_
PaymentMethod 支払い方法 pm_
Subscription サブスク sub_
PaymentIntent 支払い pi_
Charge 古い版の支払い ch_

又、どのクラスでも同じようなクラスメソッドがあります。これを覚えておくと便利です。

メソッド 意味
.create() 新規作成
.retrieve() IDを指定してオブジェクトを取得する
.list() 全部を羅列する
.search() 一部の情報によって条件に合うものを検索する
.modify() 情報更新
.delete() 削除

参考

Qiitaで今までも大量のStripe関連の記事があります。私もこれらの記事をいっぱい読んで参考にしました。全部読んだ関連の記事のリンクをここに載せておきます。

1
1
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
1
1