HPにフルスクラッチで小規模ECを導入した話

出藍文庫公式HPに、小規模なECを導入しました。

一人で、仕事終わりや休みの日にひたすら開発を続け、ChatGPTを駆使しながら形にしました。

七月頃から構想を練り、八月上旬からコードを書き、ようやく公開に至りました。

九月下旬現在はまだ商品はありません。

十月からは、文学フリマ京都で頒布する中編小説『弾かれたもの』をファン倶楽部会員向けに先行予約を実施予定です。ファン倶楽部以外の方の予約は、来年一月頃を予定しています。

ファン倶楽部については、こちらをご確認ください →「ファン倶楽部開設のお知らせ

規模は小さいですが、実装している内容自体は商業ECと同等です。

注文入力・決済・Webhook・返金・キャンセルまでを自前で扱えるので、外部サービスに依存せず運用できます。

今回は、小規模ECを導入した背景などを書いていこうと思います。

長くなります。技術的な話が多くなりますがご了承ください。

なぜ、自社に小規模なECを導入したのか

BASEShopifyBOOTHなどネットショップを開く方法はいくつもあります。

月額料金がかかるものもあれば、無料で使える代わりに収益から手数料が差し引かれるものもあります。

外部ECは便利です。しかし「そこに頼るしかない」のと「頼ることもできる上で自前を選ぶ」のとでは大きな差があります。出藍文庫では、選択肢を広げるためにも自前ECを導入しました。

また、出藍文庫では公式HPで全文を無料公開しています。外部ECの商品ページに「全文はこちら」とリンクを貼るだけでは、多くの方が全文を読まずに購入を判断してしまいます。

結果として「全文を知っていたら買わなかった」というすれ違いが起きかねません。

公式HPにECを設置すれば、無料公開と購入導線を一つの場所にまとめることができ、読者にとって納得感のある購入体験になります。

一見するとECは大きな仕組みに思えます。

ですが僕はこれまで、

• DB操作(詳細はこちら→「素人がmy SQL移行に失敗した話」)

• お問い合わせフォームの刷新

• LP内の予約フォームの作成(詳細はこちら→「架空のカフェのLPと予約フォームを作った話」)

といった開発を積み重ねてきました。

これらを組み合わせ、発展させればECの基本形は実現できるのではないか、と考えたのです。

つまり「入力フォームで注文を受け取り、その内容をDBに保存し、決済を経てDBと突き合わせて自動でメールを送信する」──この仕組みが揃えば、小規模なECとして十分に成り立つと判断しました。

そこで後は「ECとして何が欲しいのか」を考えることに集中しました。

ECとして、どういう機能が欲しいか

当時の設計メモは以下の通りです。

2025/08/07 予約管理システム(設計メモ)

予約フォーム

• 上部に「予約 → 確認 → 決済 → 完了」というステータス表示

• 名前、メールアドレス、発送先

• 注文書籍(タイトルのプルダウン)とそれぞれの冊数(半角数字で入力)

• 合計金額(注文書籍と冊数から自動計算。DB側に商品マスタがあれば容易)

複数書籍を注文できるようにする場合は、

書籍 冊数  

書籍 冊数

という表示にする。

入力後、プライバシーポリシーなどの注意事項チェックをして確認画面へ。

(サーバー側で金額を再計算。不正防止のため)

確認画面には「決済へ進む(Stripe)」「修正する」のボタンを設置。

決済ページで支払いを完了すると、注文完了画面へ移行。

注文完了画面では、入力されたメールアドレス宛に送信した「ご注文完了メール」について記載し、修正やキャンセルについての案内も表示する。

フロントエンドの動き

ユーザーが実際に触れる部分はシンプルです。

画面遷移は以下の通りです。

1. 注文フォーム

• 名前、メールアドレス、住所、注文したい書籍と数量を入力

• プライバシーポリシーや利用規約への同意チェック

• 「確認へ進む」ボタン

2. 確認画面

• 入力内容(名前・住所・商品・数量・合計金額)を表示

• 「修正する」「決済へ進む」のボタン

• 誤りがあればフォームに戻り、正しければ決済へ進む

3. 決済ページ(Stripe Checkout)

• Stripeの専用画面でクレジットカード情報を入力

• 決済完了後、自動で「注文完了画面」へ

4. 注文完了画面

• 「ご注文ありがとうございました」と表示

• 注文番号(public_code)を表示

• 入力したメールアドレス宛に完了メールを送信

バックエンドの動き(確認決済)

ユーザーから見ると、

1. 入力フォームに情報を入れる

2. 確認画面で内容を確認する

3. 決済ボタンを押す

という流れです。

しかし裏側では次の処理を行っています。

1. 確認画面表示の前に

• 入力値をサーバー側で再検証(数量、金額、送料の再計算)

• 改ざんを防ぐ

2. 決済ボタン押下時

• DBに「pending(仮注文)」を作成

• 注文内容をJSON形式で保存(商品名・数量・単価・合計)

• Stripeに「Checkout Session」を発行(metadataに注文IDを埋め込む)

3. Stripe決済完了後(Webhook受信時)

• DBの pending を paid(確定)に更新

• public_code(注文番号)を生成し、完了メールを送信

• 返金やキャンセルもWebhookで受信して更新

この仕組みにした理由

• セキュリティ:金額計算は必ずサーバー側で再実行し、改ざんを防止

• 整合性:pending → paid の二段階管理でDBと決済のズレを防ぐ

• 自動化:WebhookとDBを結合し、支払い・返金・キャンセルに応じた通知を自動送信

ChatGPTを駆使してコードを書く

設計図を元にコードを書いていきました。

設計が固まっていれば、ChatGPTに叩き台を出してもらうのも容易で、動くコードをすぐに得られます。

その後は細かい調整や修正を繰り返して形にしました。

最初に苦労したのはDBとの連携でした。

これまでPHPのコードを書くことはあっても、DBを本格的に使う経験はなく、特に「本番環境ではないテスト方法」に戸惑いました。

ローカル環境でMySQLを動かして試行錯誤しましたが、実際にサーバーへ移行すると違うエラーや設定の壁があり、何度も立ち止まりました。

以下は当時の設計メモです(現在はセキュリティ上の理由から採用していません)。

予約情報・発送の管理システム(DB)

• 独自ドメインの外に置く(LPのメールフォームのログのように)

• DB(MySQL) → ロリポップの新規データベースを利用。WordPressや独自ドメインのDBとは別にする

ログイン認証を用意する

• 外部から直アクセスできないよう制限

• DBへのログインは「メールアドレス+パスワード」の一致を確認

• 認証用URLをサブアドレスに送信(有効期限10分)

• 有効期限切れは即無効

認証用URLをクリックすると、

• DB利用用の独自ドメインURL

• メールアドレス入力画面

この二つの一致でログイン可能にする。

実際にこの構成は本番環境でも動作しましたが、最終的には個人情報保護とセキュリティの観点から、このURLへのアクセス自体を遮断しました。

DBと決済とWebhookに試行錯誤

本ECでは、ユーザーが決済に進む前にDBに「仮注文(pending)」を作成します。

これは「入力フォームで確定した注文情報」と「Stripe決済で完了した支払い情報」とを結びつけるためです。

単純に「決済が通ったら注文を作成する」という流れだと、

• DBと決済の状態がズレる

• Webhookの遅延やエラーで二重処理になる

といったリスクがあります。

そのため、次のフローを採用しました。

1. 注文入力後 → DBに pending(仮注文)を作成

2. Stripe Checkoutへリダイレクト

3. Webhookを受け取ったら DBの pending を paid(確定)に更新

時系列の流れ

2025年8月中旬:PayPalでの試行錯誤

• 最初に導入を試みたのは PayPal

• しかし Webhookから通知をDBに反映する処理がエラーで止まり、注文を確定できない問題が頻発

• 決済は済んでいるのにDBに注文が存在しない、返金やキャンセルも処理できない

• 安定運用は不可能と判断

2025年8月下旬〜9月:Stripeへの移行

• Stripeに切り替え、状況が改善

• CheckoutとWebhookがシンプルで、pending → paid の流れを実装できた

• ただしStripeもWebhookが冪等でないため、決済完了メールが2通届く問題などが発生

• イベントIDを記録し「処理済みかチェック」する仕組みを追加し安定化

学び

• 決済とDBは「仮→確定」の二段階で結ぶべき

• Webhookは必ず「冪等性チェック」を入れるべき

• 失敗の痕跡(ログ)を残すことで原因追跡が容易になる

セキュリティのためにやったこと

独自ECを構築する以上、セキュリティは自分で考える必要があります。今回の小規模ECでは次を実装しました。

• セッション管理:すべてのフォーム送信に CSRF トークンを付与。Cookieに HttpOnly・SameSite 設定

• 入力検証:郵便番号や数量の桁数制限、メール形式チェックなどを徹底

• サーバー側での再計算:金額はクライアントを信用せず再計算

• Webhookの冪等処理:イベントIDを記録して1回しか処理されないように実装

• 注文番号の複雑化:日時+ランダム値を組み合わせ、推測や攻撃を防止

• マジックリンク方式:キャンセルや会員ログインは「トークン付きワンクリックリンク」でのみ可能に

• アクセス制御:DB管理画面や内部スクリプトは .htaccess で直アクセス遮断

さらに検討した補強ポイント

• 暗号化ストレージ:個人情報をAES暗号化で保存する案(規模拡大時に導入検討)

• 二要素認証:管理画面へのメールOTP導入も検討したが現段階では見送り

スマホだけで完結できるようにWordPressプラグイン作成

DBを直接更新するにはPCでphpMyAdminを使う必要がありました。

しかし即売会終了後や外出先ではPCがなく作業が滞る可能性があります。

そこで、スマホだけで発送完了まで処理できる仕組みを考え、WordPressの管理画面で動く独自プラグインを作成しました。

• 管理画面に「注文一覧」メニューを追加

• 注文ステータスをスマホから変更可能(例:pending → paid → shipped)

• 発送完了時に追跡番号を入力 → 自動で発送完了メール送信

• 返金処理やキャンセル確定もプラグインから実行可能(Stripe APIと連携)

• すべての操作ログをWordPress側と通知ログに記録

この仕組みにより、イベント頒布後でもスマホ一台で発送処理・返金・キャンセル確定まで可能になりました。

WordPress標準の認証・権限管理を利用しているため、DB直結よりもセキュリティを確保しつつ運用できています。

自社ECの今後や拡張性

出藍文庫のECは現在、紙の本を自社から発送するモデルです。

注文が増えると発送作業がネックになります。特にイベント後や新刊時には梱包・発送に多くの時間を取られる可能性があります。

ただし、ECの仕組み自体は商業レベルに近い形で整備できており、将来的な拡張にも対応可能です。

• 発送代行や倉庫委託との連携:注文量が増えたらCSVエクスポートで外部フルフィルメントと接続可能

• 在庫・注文管理の一元化:在庫数の同期機能を追加すれば自動販売停止なども可能

• 定期刊行物や予約販売への対応:フォームやWebhookを拡張すれば定期購読も実装できる

• 販売チャネル拡張:外部ECや書店委託と併用しても、自社ECが基盤となり「どのルートで何部売れたか」を一元管理可能


当サイトでは、月に一度小説の更新をお知らせするメールマガジンを配信しております。
登録及び解除はこちらから行えます→ メールマガジン発行について

書籍の先行予約・作品の裏話の限定公開、中編の先行公開・リクエストなどの特典があるファン倶楽部を開設しております。→ ファン倶楽部開設をお知らせ