見出し画像

【アプリ開発日記53週目】nextjsに課金機能を追加する

 おはようございます、ちゅーりんです。まだ駆け出しだからか、作る度に自分が作りたいものに近づいていく感じがするそんなアプリ開発。国試無双を目指す本アプリも来週で最終回を迎え、バグを直して1月末リリースを目指します。

 これまでフレームワークやAPIなどの技術そのものに振り回されることが多かったので、ポートフォリオの時みたいにもっと高速で回せるようにしたいですね。

 そんな目新しい機能も今回で最低限揃うかもしれません。いよいよ課金機能に取り掛かっていきます!

1,テストアプリで動作確認

 今回のゴールは「購入したら支払われ、内容が見れるようになる」「販売側にその分が振り込まれる」「その一部は運営側に振り込まれる」を実装すること。

 今作っているアプリは問題セットを販売の基本単位に想定しています。わかり易い例をあげると、noteやzennなどでしょうか。下図のようにサブスクを月500円ほどで並走させることも想定しましたが、少しでもつくる人に手軽に使ってもらえるように、これらの機能は作ったとしても無料で誰でも使えるようにする予定です。

 さて実装ですが、以前と言っても一年近く前なので、ほとんど記憶に残っていません。チュートリアルなどを見ながら進めていきます。

 まずはいつものごとく、ドキュメントをみながら全く新しいアプリでテストしてみましょう。チュートリアル通り進めたものがこちら。

 テスト用のカードを入力すれば…

 無事支払い成功しました! 単一の商品販売で動作を確認できたので、今度は「AさんからBさんへ」という複数人でのstripeを実装していきます。

2,売り手のUIを作成する

 これで典型的なEコマースは作れそうです。が、今回作りたいのはユーザーAが出品したり、ユーザーBがそれを買ったりというプラットフォーム型。そこで今回は「stripe connect」というサービスを使います。

 ドキュメント「複数の売り手への支払い」で必要な条件を入力して…

URLを取得する処理を書けば

 見たことのある登録画面に飛んできました!

 stripe connectにはアカウントタイプが standard, express, custom があり、後者2つは有料(料金は同じ)、また custom は実装が手間がかかるものの自分のサイト内で登録などまで完結できてしまうという優れもの。できるならcustomにしたいところですが、不慣れでセキュリティーが心配なこともあり、今回は express にしました。

 アカウントタイプの名称は紛らわしいことに、ユーザーにとっては「stripeの画面に映るかどうか」や「ダッシュボードを見れるかどうか」が違うだけで、カスタム性はあくまでコードの手間のことを指しています。

 さて、そのまま登録情報を入力していけば、バックエンドで指定しておいたredilect_urlに戻ってきました! さらにユーザー側から自分のstripeダッシュボードを確認するリンクを取得すれば…

 stripe のマイページにも到着。これで売り手側の実装は一通りですね。なお、これらのリンクを作成するためにはユーザーごとのstripeのアカウントIDが必要。そのため、ユーザーのデータベースにこのIDを格納するスペースを追加しています。

3,買い手のUIを作成する

 さて、このままでは陳列した商品を誰も見つけることができません。ということでユーザーが商品を購入できるようにしていきます。この際どこでユーザーがカードの情報などを登録すればいいのか分からなかったため、以下のサイトを参考にさせていただきました。

 途中でパッケージ脆弱問題にへこみながら

震撼
謎の400エラーが出たので、ダッシュボードを確認したり
'card_payments' capabilityがfalseになっていたり
API使ってユーザー情報を上書きして…
ついにできた!
Woo---foooo-----!

 APIとフォーム画面を実装完了! 仮のデータを入れておいて、テスト用のクレジットカードでのお支払いもできました。

自分のウェブサイト内でフォームを表示できる!
カードを登録して
買いたい商品を押すと…
購入できた!

 またこの時痛感したことですが、途中で「セッションの読み込み早くするために先にuseContextいれるか」などと言って脇道にそれていると、途中でめちゃくちゃグダります。まず終わらせる。

 途中でファイルが増えすぎてわけがわからなくなったので、一度全体の流れをまとめておきます。

 決済処理だけでファイル10個以上になる上、最初はドキュメントもわからない言葉ばかりでかなり苦労。

 特にpaymentIntent と setupIntent はほんとごっちゃになっていたので、下記みたいにピンポイントな記事にめちゃくちゃ助けられました。ありがとうございます。

4,有料ユーザーの表示切り替え

 さきほどのstripe導入に伴い、ノートのDBに「price」カラムを追加、0なら無料、それ以外なら有料で表示が変わる、というようにしました。有料の場合中身が見れないが、ユーザーがそのノートを購入していると中身も表示される、といった仕組みです。

 しかし、このままではお金だけ払って購入したというデータがデータベースに保存されません。stripeに毎回apiを飛ばすこともできますが、案の定時間がかかってしまうため新たにデータベースに手を加えていきます。

 ポイントは

  • 親であるノート自体は、publicになっている限り誰でも見れる

  • 有料だとセクション単位で見れない

  • ただし、購入するとpurchasedBook()にそのノートのIDとユーザーのIDが追加される

  • 有料ノートのセクション一覧ページを開くと、はじめは通常通り見れない。ただし、ページを開くと同時にpurchaseBookのデータをとってきて、その自分のbooks一覧に現在開いているノートのIDがあれば、開けるようになる。また、問題リストに追加される

  • ユーザーIDをもとにデータを取得するものはuseContextに入れる?

 これらを意識して、順番に実装していきます。

 まずは、有料ノートのセクション一覧ページを変更。ここから先は今まで作ってきたページを編集していくだけでよさそうです。ただ一概にすべてのセクションを非表示にするわけにはいかないので、 セクションごとに「有料かどうか」を設定し、有料の場合は、表示されるもののリンクを押したり問題リストに追加されることはない、というようにします。

{sections && sections.map((section) => 
  !section.paid ?
    <SectionComp key={section.id} section={section} />
    :
    <SectionCompNotPaid key={section.id} section={section} />
)}
「有料」に設定しておくと選択できないように!
Freeと無料、どっちのほうがいい?

 さて、仮にユーザーがこのノートを購入したとします。すると、有料かどうかにかかわらず「無料」の状態で表示できるようにしたいところです。

 ということでさらに追加。

{isParchased ?
  (sections && sections.map((section) => 
    <SectionComp key={section.id} section={section} />
  ))
  :
  (sections && sections.map((section) => 
    !section.paid ?
      <SectionCompFree key={section.id} section={section} />
      :
      <SectionCompNotFree key={section.id} section={section} />
  ))
}
そもそも無料の場合は「無料」なども表示されなくする
if の3段構え(無料か/購入済みか/セクションは無料か)

 ここで、ノートの値段を0から1000円に変更。のちのちサイトからノートに値段をつける処理も実装したいですが、今回は手動で行います。

 すると当然、まだ購入していない自分は有料のセクションはタッチできなくなっています。

 この状態で「このノートを購入した」とDBに追加すると…

セクション3が選択できるように!

 無事追加されました!

 これで有料かどうかによる表示処理は完成ですね。今回、セクション単位で「選択できるかどうか」も制限をかけたため、新たな処理を加えなくても自然に問題リストからは除外できました。

昔頑張って作った処理の恩恵…!

 回りくどくも丁寧なDB設計にするとあとで調整しやすいです! 今回みたいな新機能を追加するときは特に楽。ただし少し重くなるので、パフォーマンス向上を目指すためには工夫が必要ですが、、、

おわりに

 今回はとにかく重かったです、、そういえば前回もtypescriptに移行していたのでそれなりでしたが、最近は徐々に慣れてきた気がします。大量のエラーを食らった分、ブラウザでコンソールを確認する前にエディタ上で未然にundefinedなどを避けられるようになったのは、意外と大きいかも。

 そして今回、ユーザー同士の売買をstripeで実装したときも、はじめはわけがわからず(nextでnode.jsのapiを使うのも初めてだった)「setup_intentとpayment_intentってなんのことやねん!!」とドキュメントとqiitaを往復して2,3日ほど費やしましたが、おかげさまでなんとか実装までこぎつけました。まだ商品登録などはできていませんが。

制作当初は予想もしていなかった光景

 いや、でも、実装できて本当によかった! 決済機能はこれから本当に重宝しそうなので!

 そんなわけで、次回は商品登録や購入記録を加えていきます。2ヶ月強にわたる問題アプリの最終回です!

 ではでは!

この記事が気に入ったらサポートをしてみませんか?