Open61

読者コミュニティ|【実践 Ruby on Rails】Stripe を使って EC サイトを作ろう(Rails 7 対応)

FarStepFarStep

本の感想や質問をお気軽にコメントしてください。

https://zenn.dev/farstep/books/7f169cdc597ada

hiratahirata

失礼致します。
本記事にてアプリを作成しているのですが分からないことがございます。
12の決済処理を実装仕様にて決済が完了してリダイレクトされた後にカート内が0にならず更新されないです。
こちらのコミュニティでcreate orderメソッドの中のshippingをshipping_detailsに変更して再度決済を
完了してもカートの中身はそのままです。😰
解決方法教えて頂けないでしょうか🙏

補足
rails cにてendpointも登録、返信されましたが個人的にはstripeからcreate orderの情報が送られてない関連
だと思うのですが初心者なものでよく分かりません(~_~;)

webhooks_controller.rb


class Customer::WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token

def create
payload = request.body.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
endpoint_secret = Rails.application.credentials.dig(:stripe, :endpoint_secret)
event = nil

begin
  event = Stripe::Webhook.construct_event(
    payload, sig_header, endpoint_secret
  )
rescue JSON::ParserError => e
  # Invalid payload
  p e
  status 400
  return
rescue Stripe::SignatureVerificationError => e
  # Invalid signature
  p e
  status 400
  return
end

case event.type
when 'checkout.session.completed'
  session = event.data.object # sessionの取得
  customer = Customer.find(session.client_reference_id)
  return unless customer # 顧客が存在するかどうか確認

  # トランザクション処理開始
  ApplicationRecord.transaction do
    order = create_order(session) # sessionを元にordersテーブルにデータを挿入
    session_with_expand = Stripe::Checkout::Session.retrieve({ id: session.id, expand: ['line_items'] })
    session_with_expand.line_items.data.each do |line_item|
      create_order_details(order, line_item) # 取り出したline_itemをorder_detailsテーブルに登録
    end
  end
  # トランザクション処理終了
  customer.cart_items.destroy_all # 顧客のカート内商品を全て削除
  OrderMailer.complete(email: session.customer_details.email).deliver_later
  redirect_to session.success_url
end

end

private

def create_order(session)
Order.create!({
customer_id: session.client_reference_id,
name: session.shipping.name,
postal_code: session.shipping.address.postal_code,
prefecture: session.shipping.address.state,
address1: session.shipping.address.line1,
address2: session.shipping.address.line2,
postage: session.shipping_options[0].shipping_amount,
billing_amount: session.amount_total,
status: 'confirm_payment'
})
end

def create_order_details(order, line_item)
product = Stripe::Product.retrieve(line_item.price.product)
purchased_product = Product.find(product.metadata.product_id)
raise ActiveRecord::RecordNotFound if purchased_product.nil?

order_detail = order.order_details.create!({
                                             product_id: purchased_product.id,
                                             price: line_item.price.unit_amount,
                                             quantity: line_item.quantity
                                           })
purchased_product.update!(stock: (purchased_product.stock - order_detail.quantity)) # 購入された商品の在庫数の更新

end
end

ponponboyponponboy

先月から取り組ませていただき、3週間程度かけて本日ハンズオンを終えました!
お手本通りにアプリを完成させることができました。素晴らしい記事をありがとうございます。
とても実践的な内容と感じました。Railsに加え、Stripeの利用方法の理解が深まりました。

何かお役にたちたく、以下に誤記と思われた箇所を記載いたします。
・chapter 09: 厚固め→扱うため と思われます
・chapter 12: 「それでは、app/controllers/customer/checkouts_controller.rb を開いて下記コードを記述してください。」⇦記事中頃のこちらの文言は消し忘れのように思われました。
・chapter 13: 記事中頃の"config.time_zone = 'Tokyo'"の記述を追加するところのコード部について、chapter05で追加した「8. config/application.rb の編集」分が反映されていない模様でした。
・chapter 17: 「app/controllers/customer/orders_controller.rb」→customers_controller.rb コード部のファイル名が異なっているようでした。

以上です、ありがとうございました!

FarStepFarStep

こんにちは。
嬉しいお言葉ありがとうございます。お役に立てて何よりです。
何よりこんなにも長い書籍を最後まで読んでいただきありがとうございました!

誤植についてのご指摘もありがとうございます。こういったコメント非常に助かります 😭
先ほど全ての誤植を修正いたしました。

これからもみなさまのお役に立てるよう、精進したいと思います。

今後ともよろしくお願いします!

YuriYuri

下記ルーティングの設定の箇所で詰まっています。回答いただけると助かります泣

実はYoutubeのRecipegramの動画でもDeviseでのルーティングがうまくいかず挫折し、こちらの教材を発見し再挑戦しましたが、また同じような状況になってしまいました。

ターミナルでルーティングの状況を見ると大丈夫そうなのですが、ブラウザでhttp://localhost:8000/を開いた際、下記のようなエラーが出てしまいます。。(routes.rbのファイルを元に戻せば、Railsのデフォルトの画面は表示されます)

Recipegramの際はrailsやRubyのバージョンが動画と違う最新のものを使ってしまっていたため、今回はできるだけバージョンを合わせて作成しましたが、またエラーになってしまいました。

Ruby 2.5.7
Rails 7.0.4
Homebrew 3.6.16
Xcode Version 14.2 (14C18)
rbenv 1.2.0
Docker version 20.10.21, build baeda1f
Docker Compose version v2.13.0

アドバイスいただけると非常に助かります。よろしくお願いします!

ルーティングの設定

FarStepFarStep

こんにちは。
丁寧にご質問して頂き誠にありがとうございます。
Youtube もご覧頂き嬉しいです 😊

ルーティングエラーが吐かれているとのこと承知しました。
大変お手数なのですが、現在実装されている

  • routes.rb の記述
  • ディレクトリ構成(ディレクトリの名前を確認させて頂きたいです)
  • controllers/admin/sessions_controller.rb の記述

以上三点について教えていただくことは可能でしょうか。
何卒よろしくお願いします。

YuriYuri

返信ありがとうございます😭ハンズオンでは最後まで出来ませんでしたが、動画の内容もすごく良かったです。

特に何も変更してないのですが今ブラウザを開いたら違うエラーになってました。。
そちらも添付しておきます。

・routes.rb の記述

routes.rb
Rails.application.routes.draw do
  # devise_for :customers
  # devise_for :admins

  devise_for :admins, controllers: {
    sessions: 'admin/sessions'
  }
  devise_for :customers, controllers: {
    sessions: 'customer/sessions',
    registrations: 'customer/registrations'
  }
  root to: 'pages#home'
  get '/up/', to: 'up#index', as: :up
  get '/up/databases', to: 'up#databases', as: :up_databases

  # Sidekiq has a web dashboard which you can enable below. It's turned off by
  # default because you very likely wouldn't want this to be available to
  # everyone in production.
  #
  # Uncomment the 2 lines below to enable the dashboard WITHOUT authentication,
  # but be careful because even anonymous web visitors will be able to see it!
  # require "sidekiq/web"
  # mount Sidekiq::Web => "/sidekiq"
  #
  # If you add Devise to this project and happen to have an admin? attribute
  # on your user you can uncomment the 4 lines below to only allow access to
  # the dashboard if you're an admin. Feel free to adjust things as needed.
  # require "sidekiq/web"
  # authenticate :user, lambda { |u| u.admin? } do
  #   mount Sidekiq::Web => "/sidekiq"
  # end

  # Learn more about this file at: https://guides.rubyonrails.org/routing.html
end

・ディレクトリ構成(かなり長くなってしまいましたがので一部削除したものをこちらには記載してます。空白の行のところは削除したものがあるところです。もちろん実際のファイルは削除してません)

.
├── .git
│   ├── hooks
│   │   ├── applypatch-msg.sample
│   │   ├── commit-msg.sample
│   │   ├── fsmonitor-watchman.sample
│   │   ├── post-update.sample
│   │   ├── pre-applypatch.sample
│   │   ├── pre-commit.sample
│   │   ├── pre-merge-commit.sample
│   │   ├── pre-push.sample
│   │   ├── pre-rebase.sample
│   │   ├── pre-receive.sample
│   │   ├── prepare-commit-msg.sample
│   │   ├── push-to-checkout.sample
│   │   └── update.sample
│   ├── info
│   │   └── exclude
│   ├── logs
│   │   ├── refs
│   │   │   ├── heads
│   │   │   │   └── main
│   │   │   └── remotes
│   │   │       └── origin
│   │   │           └── main
│   │   └── HEAD
│   ├── objects
│   │   ├── 00

│   │   ├── info
│   │   └── pack
│   ├── refs
│   │   ├── heads
│   │   │   └── main
│   │   ├── remotes
│   │   │   └── origin
│   │   │       └── main
│   │   └── tags
│   ├── COMMIT_EDITMSG
│   ├── config
│   ├── description
│   ├── HEAD
│   └── index
├── .github
│   ├── docs
│   │   └── screenshot.jpg
│   └── workflows
│       └── ci.yml
├── app
│   ├── assets
│   │   ├── builds
│   │   │   ├── .keep
│   │   │   ├── application.css
│   │   │   ├── application.js
│   │   │   └── application.js.map
│   │   ├── config
│   │   │   └── manifest.js
│   │   ├── images
│   │   │   ├── .keep
│   │   │   └── ruby-on-rails.png
│   │   └── stylesheets
│   │       └── application.tailwind.css
│   ├── channels
│   │   └── application_cable
│   │       ├── channel.rb
│   │       └── connection.rb
│   ├── controllers
│   │   ├── admin
│   │   │   ├── confirmations_controller.rb
│   │   │   ├── omniauth_callbacks_controller.rb
│   │   │   ├── passwords_controller.rb
│   │   │   ├── registrations_controller.rb
│   │   │   ├── sessions_controller.rb
│   │   │   └── unlocks_controller.rb
│   │   ├── concerns
│   │   │   └── .keep
│   │   ├── customer
│   │   │   ├── confirmations_controller.rb
│   │   │   ├── omniauth_callbacks_controller.rb
│   │   │   ├── passwords_controller.rb
│   │   │   ├── registrations_controller.rb
│   │   │   ├── sessions_controller.rb
│   │   │   └── unlocks_controller.rb
│   │   ├── application_controller.rb
│   │   ├── pages_controller.rb
│   │   └── up_controller.rb
│   ├── helpers
│   │   ├── application_helper.rb
│   │   └── pages_helper.rb
│   ├── javascript
│   │   ├── controllers
│   │   │   ├── .keep
│   │   │   ├── application.js
│   │   │   └── index.js
│   │   └── application.js
│   ├── jobs
│   │   └── application_job.rb
│   ├── mailers
│   │   └── application_mailer.rb
│   ├── models
│   │   ├── concerns
│   │   │   └── .keep
│   │   ├── admin.rb
│   │   ├── application_record.rb
│   │   └── customer.rb
│   ├── views
│   │   ├── admin
│   │   │   ├── confirmations
│   │   │   │   └── new.html.erb
│   │   │   ├── mailer
│   │   │   │   ├── confirmation_instructions.html.erb
│   │   │   │   ├── email_changed.html.erb
│   │   │   │   ├── password_change.html.erb
│   │   │   │   ├── reset_password_instructions.html.erb
│   │   │   │   └── unlock_instructions.html.erb
│   │   │   ├── passwords
│   │   │   │   ├── edit.html.erb
│   │   │   │   └── new.html.erb
│   │   │   ├── registrations
│   │   │   │   ├── edit.html.erb
│   │   │   │   └── new.html.erb
│   │   │   ├── sessions
│   │   │   │   └── new.html.erb
│   │   │   ├── shared
│   │   │   │   ├── _error_messages.html.erb
│   │   │   │   └── _links.html.erb
│   │   │   └── unlocks
│   │   │       └── new.html.erb
│   │   ├── customer
│   │   │   ├── confirmations
│   │   │   │   └── new.html.erb
│   │   │   ├── mailer
│   │   │   │   ├── confirmation_instructions.html.erb
│   │   │   │   ├── email_changed.html.erb
│   │   │   │   ├── password_change.html.erb
│   │   │   │   ├── reset_password_instructions.html.erb
│   │   │   │   └── unlock_instructions.html.erb
│   │   │   ├── passwords
│   │   │   │   ├── edit.html.erb
│   │   │   │   └── new.html.erb
│   │   │   ├── registrations
│   │   │   │   ├── edit.html.erb
│   │   │   │   └── new.html.erb
│   │   │   ├── sessions
│   │   │   │   └── new.html.erb
│   │   │   ├── shared
│   │   │   │   ├── _error_messages.html.erb
│   │   │   │   └── _links.html.erb
│   │   │   └── unlocks
│   │   │       └── new.html.erb
│   │   ├── layouts
│   │   │   ├── application.html.erb
│   │   │   ├── mailer.html.erb
│   │   │   └── mailer.text.erb
│   │   ├── pages
│   │   │   └── home.html.erb
│   │   └── .DS_Store
│   └── .DS_Store
├── bin
│   ├── docker-entrypoint-web
│   ├── rails
│   ├── rake
│   ├── rename-project
│   ├── setup
│   └── yarn
├── cable
│   └── config.ru
├── config
│   ├── environments
│   │   ├── development.rb
│   │   ├── production.rb
│   │   └── test.rb
│   ├── initializers
│   │   ├── assets.rb
│   │   ├── content_security_policy.rb
│   │   ├── devise.rb
│   │   ├── filter_parameter_logging.rb
│   │   ├── inflections.rb
│   │   ├── permissions_policy.rb
│   │   ├── rack_mini_profiler.rb
│   │   ├── redis.rb
│   │   └── sidekiq.rb
│   ├── locales
│   │   ├── devise.en.yml
│   │   └── en.yml
│   ├── .DS_Store
│   ├── application.rb
│   ├── boot.rb
│   ├── cable.yml
│   ├── database.yml
│   ├── environment.rb
│   ├── puma.rb
│   ├── routes.rb
│   ├── sidekiq.yml
│   ├── spring.rb
│   └── storage.yml
├── db
│   ├── migrate
│   │   ├── 20230110185440_devise_create_admins.rb
│   │   └── 20230110185533_devise_create_customers.rb
│   ├── schema.rb
│   └── seeds.rb
├── lib
│   ├── assets
│   │   └── .keep
│   └── tasks
│       └── .keep
├── log
│   └── .keep
├── public
│   ├── 502.html
│   ├── android-chrome-192x192.png
│   ├── android-chrome-512x512.png
│   ├── apple-touch-icon.png
│   ├── browserconfig.xml
│   ├── favicon-16x16.png
│   ├── favicon-32x32.png
│   ├── favicon.ico
│   ├── maintenance.html
│   ├── mstile-150x150.png
│   ├── robots.txt
│   ├── safari-pinned-tab.svg
│   └── site.webmanifest
├── storage
│   └── .keep
├── test
│   ├── channels
│   │   └── application_cable
│   │       └── connection_test.rb
│   ├── controllers
│   │   ├── .keep
│   │   ├── pages_controller_test.rb
│   │   └── up_controller_test.rb
│   ├── fixtures
│   │   └── files
│   │       └── .keep
│   ├── helpers
│   │   └── .keep
│   ├── integration
│   │   └── .keep
│   ├── mailers
│   │   └── .keep
│   ├── models
│   │   └── .keep
│   ├── system
│   │   └── .keep
│   ├── application_system_test_case.rb
│   └── test_helper.rb
├── tmp
│   ├── cache
│   │   ├── assets
│   │   │   └── sprockets
│   │   │       └── v4.0.0

│   │   └── bootsnap
│   │       ├── compile-cache-iseq

│   │       └── load-path-cache
│   ├── miniprofiler

│   ├── pids
│   │   └── server.pid
│   ├── sockets
│   ├── .keep
│   ├── development_secret.txt
│   └── restart.txt
├── vendor
│   └── .keep
├── .dockerignore
├── .DS_Store
├── .env
├── .env.example
├── .gitignore
├── .rubocop.yml
├── .yarnrc
├── CHANGELOG.md
├── config.ru
├── docker-compose.yml
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── package.json
├── postcss.config.js
├── Rakefile
├── README.md
├── run
├── tailwind.config.js
└── yarn.lock

・controllers/admin/sessions_controller.rb の記述

controllers/admin/sessions_controller.rb
# frozen_string_literal: true

class Admin::SessionsController < Devise::SessionsController
  # before_action :configure_sign_in_params, only: [:create]

  # GET /resource/sign_in
  # def new
  #   super
  # end

  # POST /resource/sign_in
  # def create
  #   super
  # end

  # DELETE /resource/sign_out
  # def destroy
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_in_params
  #   devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
  # end
end

よろしくお願いします!

FarStepFarStep

ありがとうございます!
コード・ディレクトリ構成は問題なさそうですね。

一つ確認させて頂きたいのですが、下記コマンドは実行されましたでしょうか。

$ rails g devise:install
FarStepFarStep

ありがとうございます!

$ rails db:migrate

も実行されていますでしょうか。
schema.rb も見せて頂けると助かります!

YuriYuri

はい!出来ているかと。お手数おかけしてすみません😅

schema.rb
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2023_01_10_185533) do
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "admins", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_admins_on_email", unique: true
    t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
  end

  create_table "customers", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.string "name", null: false
    t.integer "status", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_customers_on_email", unique: true
    t.index ["reset_password_token"], name: "index_customers_on_reset_password_token", unique: true
  end

end
FarStepFarStep

なるほど!ありがとうございます!
手順もあってそうですねー🤔

そうしましたら大変お手数なのですが、GitHub 上に開発途中のコードをプッシュして頂くことは可能でしょうか。

私の方で動作確認させて頂きたいです!

YuriYuri

すみません。ありがとうございます😭
Deviseのルーティング問題で1週間くらいRailsの学習が進められておらず、とても助かります。
一応チャプター7の作業は終えている状態かと思います。

こちらに載せておきます。
Github

FarStepFarStep

ありがとうございます!
早速動作確認してみますね!
今出先ですので回答は明日になってしまうと思いますが、お待ちいただけると幸いです。

YuriYuri

ありがとうございます!お待ちしてます^^

FarStepFarStep

お待たさせしました!
先ほどいただいた GitHub コードを clone してきて環境構築を行ったところ、新規登録画面・ログイン画面ともに表示されました。
実行したコマンドは下記の通りです。

$ git clone git@github.com:yurikondo/ecommerce.git
$ cd ecommerce
$ docker-compose build --no-cache
$ docker-compose run --rm web bash

下記は、コンテナの中で実行しています。

$ rails db:create
$ rails db:migrate

コンテナからログアウトして、下記コマンドを実行しています。

$ docker-compose up

大変お手数ですが、一度

  • コンテナの削除
  • ボリュームの削除
  • イメージの削除

を行っていただいて、上記の通りコマンドを実行して頂いてもよろしいでしょうか。
何卒よろしくお願いいたします。

YuriYuri

http://localhost:8000/customers/sign_in で表示される画面は現時点だと画像の通りで合ってますよね?
本当にありがとうございます😭

エラーの原因としてはこちらのコマンドを打つ際にキャッシュか何かが残ってたのが原因だったのでしょうか。。。?今後またDockerを使う際は「--no-cache」を基本つけたほうがいいでしょうか?

$ docker-compose build --no-cache

FarStepFarStep

そちらの画面で合っております!
エラーの原因については特定できませんでした。すみません🙏
キャッシュが影響していた可能性はありますね。

下記コマンドについては、build を実行する際に参照ファイルを更新したにもかかわらず、反映されない場合によく使いますね。

$ docker-compose build --no-cache

キャッシュを無効にしたいと言う場面でのみ --no-cache をつければいいと思います。

YuriYuri

一気にこちらの教材を進めております!

決済処理の動作確認をしよう

現在ここまできたのですが、Dockerコンテナ内で下記コマンドを実行し、Stripe CLIをインストするという流れであってますでしょうか?

FarStepFarStep

ご質問頂きありがとうございます!

Stripe CLI は Docker コンテナ内ではなく、ローカル にインストールするようお願いします。
Stripe CLI のコマンドをローカルで叩くことになるので!

Mac でしたら下記コマンドになりますね。

$ brew install stripe/stripe-cli/stripe
isoseaisosea

現在Chapter 7「認証機能を実装しよう」のあたりを進めています。
以下のコマンドについて質問があります。

$ rails g devise:controllers admin
$ rails g devise:controllers customer
$ rails g devise:views admins
$ rails g devise:views customers

こちらを実行後、views フォルダ配下のadmins, customersをadmin, customerに変更し、単数形に揃えていますが、controllerを生成するコマンドを

$ rails g devise:controllers admins
$ rails g devise:controllers customers

として複数形に揃えることはできないのでしょうか?
こうすれば修正が必要なくなるのではないかと思ったのですが...

<追記>
Chapter7終了時点で http://localhost:8000/ にアクセスすると、上の質問者さんと同じように以下のようなエラーに遭遇しました。

後日コンテナを再起動すると直ったので、原因はわかりませんでしたがお知らせしておきます。

FarStepFarStep

こんにちは。
本書籍をご覧いただきありがとうございます!

【views フォルダ配下の名前について】
今回単数系にしたのは、名前空間を使用する際に単数系にした方が better かなと考えたためです。Rails ガイドでのディレクトリ名も admin と単数系になっていました。
https://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
ですがおっしゃる通り、複数形のままにすれば修正が不要になりますので、どちらでもよろしいかと思います。ありがとうございます。

【Chapter 07 終了時点でのエラーについて】
エラーが発生するとのご指摘ありがとうございます。
コンテナの再起動が必要であるとの旨を追加しました。
こういったコメント非常に助かります 😭

他にご不明点などあればコメントして頂けると幸いです。

isoseaisosea

ご回答ありがとうございます!
こちらこそ、こういった質問できる環境があるととても嬉しいです!
この教材も完走できるようにがんばります💪

名前空間については単数形/複数形どちらでも機能的には問題ないのですね。
自分でも少し調べてみたんですが、deviseのGitHubを見るとやはりcontrollerの生成はscopeに合わせるようです。
https://github.com/heartcombo/devise#configuring-controllers
exampleでも複数形のものがありました。
https://github.com/imhta/rails_6_devise_example

FarStepFarStep

ありがとうございます!

なるほど、devise の README.md によると scope に合わせているんですね。
だとすると、名前空間をわざわざ単数形に変更する必要はなかったかもしれないです...

勉強になりました。
ありがとうございました。

今後ともよろしくお願いします!

tatsuru_hisanagatatsuru_hisanaga

はじめまして!

こちらの記事を参考にwebアプリ開発の練習をしているのですが、「UIを作成しよう」の部分でつまづいてしまっています。

具体的には、(おそらく)記事の通りにerbファイルを書いているにもかかわらず、UIが下記のようにcssがうまく反映されません。

home.html.erb
<div class='pt-32 pb-12 md:pt-40 md:pb-20'>
  <div class='pb-12 text-center md:pb-16'>
    <h1 class='leading-tighter mb-4 text-7xl font-extrabold tracking-tighter md:text-8xl'>
      <span class="p-2 bg-gradient-to-right from-purple-600 to-blue-500 text-transparent bg-clip-text">
        ECommerce
      </span>

    </h1>
    <div class='mx-auto max-w-3xl'>
      <p class='mb-8 text-xl'>You will find what you are looking for.</p>
      <div class='flex justify-center mt-5'>
        <div class='mt-3'>
          <%= link_to root_path, class:'group inline-flex items-center justify-center overflow-hidden rounded-lg bg-gradient-to-br from-purple-600 to-blue-500 p-0.5 font-medium text-gray-900 hover:text-white focus:ring-4 focus:ring-blue-300 group-hover:from-purple-600 group-hover:to-blue-500' do %>
            <span class='rounded-md bg-white px-5 py-2.5 transition-all duration-75 ease-in group-hover:bg-opacity-0'>
              Find Products
            </span>
          <% end %>
        </div>
      </div>
    </div>
  </div>
</div>

トップページだけでなく、管理者や顧客の認証画面でも同様にcssが上手く適用されません。。。
何が原因でこうなってしまうのでしょうか?
お力添え頂けると非常にありがたいですm(__)m

FarStepFarStep

こんにちは。
ご質問頂きありがとうございます!

もしかすると ecommerce_css コンテナによるビルドがうまく動作していないのかもしれません。
再度ビルドし直すことは可能でしょうか。具体的には、下記コマンドを実行して頂ければと思います。データベースに保存されているデータは消失しますので、ご注意ください。

  1. 一度コンテナを削除する。
$ docker-compose down
  1. ボリュームを削除する。
$ docker volume prune -f
  1. キャッシュを使わずにビルドする。
$ docker-compose build --no-cache
  1. コンテナの中に入り、データベースのセットアップを行う。
$ rails db:create
$ rails db:migrate
  1. コンテナを立ち上げる。
$ docker-compose up

上記で解決しない場合、再度ご質問頂けると幸いです。
何卒よろしくお願いします。

tatsuru_hisanagatatsuru_hisanaga

迅速かつ丁寧なご対応ありがとうございます!
アドバイスのとおりに作業してみたところ、無事に解決できました!ありがとうございます!

また、異なる質問重ねて申し訳ないのですが、サインアップ時に名前を入力したにもかかわらず、バリデーションに引っかかってしまいます。自力で解決を試みたのですが原因をはっきりと特定できず、行きづまっている状態です。
何か推測できるエラーの原因や解決策はありませんでしょうか?

FarStepFarStep

いえいえ、解決できて何よりです!

【サインアップ時にバリデーションに引っかかってしまう件について】
フォームに名前を入力したにも関わらず、バリデーションに引っかかってしまう場合は、コントローラへデータを送信する際に、name パラメータが許可されていない可能性があります。

下記の節の パラメータを許可 が正しく行われているかどうか確認してみてください。
https://zenn.dev/farstep/books/7f169cdc597ada/viewer/014395#認証機能の設定

ちなみにパラメータが許可されていない場合、Unpermitted parameter というログが表示されていると思います。

解決しない場合は、再度ご質問して頂けると幸いです。
何卒よろしくお願いします。

tatsuru_hisanagatatsuru_hisanaga

解決できました!
ご指摘のとおり、パラメータを許可できていなかったようです。
ありがとうございます!😊

FarStepFarStep

解決してよかったです!

これからも全力でサポートさせて頂きますので、また何か疑問点などありましたらお気軽にコメントして頂けると幸いです。今後ともよろしくお願いします。

tatsuru_hisanagatatsuru_hisanaga

こんばんは😊
現在11章のビュー作成の部分まで進んでいるのですが、カートに商品を追加するボタンを押すと下のようなルーティングエラーが発生してしまいます。

商品は現在2つ登録していて、どちらの商品もカートに追加できない状態です。
知識不足でどうすればよいか分からず困っています。

何かアドバイス頂けないでしょうか

以下現在の自分のコードです

routes.rb
Rails.application.routes.draw do
  devise_for :admins, controllers: {
    sessions: 'admin/sessions'
  }
  devise_for :customers, controllers: {
    sessions: 'customer/sessions',
    registrations: 'customer/registrations'
  }

  root to: 'pages#home'
  namespace :admin do
    resources :products, only: %i[index show new create edit update]
  end
  scope module: :customer do
    resources :products, only: %i[index show]
    resources :cart_items, only: %i[index create destroy] do
      member do
        patch 'increase'
        patch 'decrease'
      end
    end
  end

  get '/up/', to: 'up#index', as: :up
  get '/up/databases', to: 'up#databases', as: :up_databases
  get '/', to: 'pages#home'
  # Sidekiq has a web dashboard which you can enable below. It's turned off by
  # default because you very likely wouldn't want this to be available to
  # everyone in production.
  #
  # Uncomment the 2 lines below to enable the dashboard WITHOUT authentication,
  # but be careful because even anonymous web visitors will be able to see it!
  # require "sidekiq/web"
  # mount Sidekiq::Web => "/sidekiq"
  #
  # If you add Devise to this project and happen to have an admin? attribute
  # on your user you can uncomment the 4 lines below to only allow access to
  # the dashboard if you're an admin. Feel free to adjust things as needed.
  # require "sidekiq/web"
  # authenticate :user, lambda { |u| u.admin? } do
  #   mount Sidekiq::Web => "/sidekiq"
  # end

  # Learn more about this file at: https://guides.rubyonrails.org/routing.html
end
cart_items_controller.rb
class Customer::CartItemsController < ApplicationController
  before_action :authenticate_customer!
  before_action :set_cart_item, only: %i[increase decrease destroy]

  def index
    @cart_items = current_customer.cart_items
  end

  def create
    increase_or_create(params[:cart_item][:product_id])
    redirect_to cart_items_path, notice: 'Successfully added product to your cart'
  end

  def increase
    @cart_item.increment!(:quantity, 1)
    redirect_to request.referer, notice: 'Successfully updated your cart'
  end

  def decrease
    decrease_or_destroy(@cart_item)
    redirect_to request.referer, notice: 'Successfully updated your cart'
  end

  def destroy
    @cart_item.destroy
    redirect_to request.referer, notice: 'Successfully deleted one cart item'
  end

  private

  def set_cart_item
    @cart_item = current_customer.cart_items.find(params[:id])
  end

  def increase_or_create(product_id)
    cart_item = current_customer.cart_items.find_by(product_id:)
    if cart_item
      cart_item.increment!(:quantity, 1)
    else
      current_customer.cart_items.build(product_id:).save
    end
  end

  def decrease_or_destroy(cart_item)
    if cart_item.quantity > 1
      cart_item.decrement!(:quantity, 1)
    else
      cart_item.destroy
    end
  end
end

FarStepFarStep

こんばんは。
ご質問頂きありがとうございます!

Routing Error ですので、存在しないルーティングにアクセスしてしまっているのがエラーの原因ですね。商品をカートに追加するボタンのパスを今一度ご確認ください。

下記のようなコードが記載されているでしょうか?

<%= form_with model: @cart_item, data: { turbo: false } do |f| %>
  <%= f.hidden_field :product_id, :value => @product.id %>
  <%= f.submit "Add to Cart", class:"w-full cursor-pointer focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2" %>
<% end %>

app/views/customer/products/show.html.erb のコードも見せていただけると幸いです。
何卒よろしくお願いします。

tatsuru_hisanagatatsuru_hisanaga

ご指摘のコードは以下のようになっています。
よろしくお願いします🙇‍♂️

app/views/customer/products/show.html.erb
    <div class="relative mx-auto max-w-screen-xl p-6">
        <div class="grid grid-cols-1 items-start gap-9 md:grid-cols-2">
            <div>
            <%= image_tag @product.image, class: "aspect-square w-full rounded-xl object-cover" %>
            </div>
            <div class="sticky top-0">
                <div class="flex flex-col justify-between">
                    <div class="flex justify-between mb-6">
                        <div class="max-w-[35ch]">
                            <h1 class="text-2xl font-bold">
                                <%= @product.name %>
                            </h1>
                        </div>
                        <p class="text-2xl font-bold"><%= number_to_currency(@product.price, unit: "¥", strip_insignificant_zeros: true) %></p>
                    </div>
                    <div class="mb-3">
                        <p>
                            <%= @product.description %>
                        </p>
                    </div>
                    <div class="mb-8">
                        <% if @product.stock > 0 %>
                            <span class="bg-blue-100 text-blue-800 text-xs font-semibold p-2 rounded">In stock (<%= @product.stock %>)</span>
                        <% else %>
                            <span class="bg-red-100 text-red-800 text-xs font-semibold p-2 rounded">Out of stock</span>
                        <% end %>
                    </div>
                    <% if @product.stock > 0 %>
                        <%= form_with model: @cart_item, data: { turbo: false } do |f| %>
                            <%= f.hidden_field :product_id, :value => @product.id %>
                            <%= f.submit "Add to Cart", class:"w-full cursor-pointer focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2" %>
                        <% end %>
                    <% end %>
                </div>
            </div>
        </div>
    </div>
FarStepFarStep

ご返信頂きありがとうございます。
ビューの記述は合ってるようです。

app/controllers/customer/products_controller.rb に下記の記述はありますでしょうか。

def show
  @product = Product.find(params[:id])
  @cart_item = CartItem.new
end
tatsuru_hisanagatatsuru_hisanaga

夜分遅くにお返事いただきありがとうございます。
確認してみたところ、@cart_item とすべきところを @cart_items としてしまっていました。
修正後、正しく動作しました!
ありがとうございます😊

FarStepFarStep

解決してよかったです!

また何か疑問点などありましたらお気軽にコメントして頂けると幸いです。
今後ともよろしくお願いします!

tatsuru_hisanagatatsuru_hisanaga

質問すみません!
11章のカート追加の部分で「ログから INSERT INTO "cart_items" という SQL 文が発行されていることを確認する」とありますが、どのようにすればログを確認できますか。

FarStepFarStep

ご返信が遅れて申し訳ありません。
ログというのは、ターミナルのコンテナログのことですね。
解決したようでよかったです!

isoseaisosea

12章の決済処理の部分まできました!
自己解決したのですが、最後の動作確認の部分でエラーになってしまったのでお知らせです。

2023-02-16 21:04:06   --> payment_intent.created [evt_3Mc6C0GMeb4ApE5J0HlWnG5W]
2023-02-16 21:04:06   --> payment_intent.succeeded [evt_3Mc6C0GMeb4ApE5J0m8H0cQz]
2023-02-16 21:04:06   --> charge.succeeded [evt_3Mc6C0GMeb4ApE5J07UKoAjU]
2023-02-16 21:04:06   --> checkout.session.completed [evt_1Mc6C2GMeb4ApE5JoSP5fdG1]
2023-02-16 21:04:07  <--  [500] POST http://localhost:8000/webhooks [evt_1Mc6C2GMeb4ApE5JoSP5fdG1]
2023-02-16 21:04:07  <--  [204] POST http://localhost:8000/webhooks [evt_3Mc6C0GMeb4ApE5J07UKoAjU]
2023-02-16 21:04:07  <--  [204] POST http://localhost:8000/webhooks [evt_3Mc6C0GMeb4ApE5J0m8H0cQz]
2023-02-16 21:04:07  <--  [204] POST http://localhost:8000/webhooks [evt_3Mc6C0GMeb4ApE5J0HlWnG5W]

checkout.session.completedのイベントでエラーになっています。
ログではshippingメソッドが無いと言われていました。

NoMethodError (undefined method `shipping' for #<Stripe::Checkout::Session:0x7a80 id=xxx>

Stripeのドキュメントを見てもshippingというキーは無さそうです。
https://stripe.com/docs/api/checkout/sessions/object
webhooks_controller.rbcreate_orderメソッドの中で、shippingの部分をshipping_detailsとすることで解決しました。

FarStepFarStep

こんにちは。
引き続き本書籍をご覧いただきありがとうございます!

shipping メソッドが存在しない件について、ご指摘いただきありがとうございます。

先ほど、完成版のソースコードをクローンしてきて、決済処理を行ったところ正常に注文が完了してしまいました。session オブジェクトの中身をログに吐き出してみたのですが、なぜか shipping というキーが存在していました。原因は不明です...

web_1       |   Customer Load (1.0ms)  SELECT "customers".* FROM "customers" WHERE "customers"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
web_1       |   ↳ app/controllers/customer/webhooks_controller.rb:29:in `create'
web_1       | {
web_1       |   "id": "cs_test_b1t9BcngGc2sbb3UoJ3xQ6MPVPthyRYQC7XKhGCJDuOtK2ldFKRBT47NmA",
web_1       |   "object": "checkout.session",
web_1       |   "after_expiration": null,
web_1       |   "allow_promotion_codes": null,
web_1       |   "amount_subtotal": 14760,
web_1       |   "amount_total": 15260,
web_1       |   "automatic_tax": {
web_1       |     "enabled": false,
web_1       |     "status": null
web_1       |   },
web_1       |   "billing_address_collection": null,
web_1       |   "cancel_url": "http://localhost:8000/cart_items",
web_1       |   "client_reference_id": "2",
web_1       |   "consent": null,
web_1       |   "consent_collection": null,
web_1       |   "created": 1676811086,
web_1       |   "currency": "jpy",
web_1       |   "custom_fields": [
web_1       |
web_1       |   ],
web_1       |   "custom_text": {
web_1       |     "shipping_address": null,
web_1       |     "submit": null
web_1       |   },
web_1       |   "customer": null,
web_1       |   "customer_creation": "if_required",
web_1       |   "customer_details": {
web_1       |     "address": {
web_1       |       "city": null,
web_1       |       "country": "JP",
web_1       |       "line1": "檜原村",
web_1       |       "line2": null,
web_1       |       "postal_code": "190-0200",
web_1       |       "state": "東京都"
web_1       |     },
web_1       |     "email": "yamada.taro@gmail.com",
web_1       |     "name": "yamada taro",
web_1       |     "phone": null,
web_1       |     "tax_exempt": "none",
web_1       |     "tax_ids": [
web_1       |
web_1       |     ]
web_1       |   },
web_1       |   "customer_email": "yamada.taro@gmail.com",
web_1       |   "expires_at": 1676897486,
web_1       |   "invoice": null,
web_1       |   "invoice_creation": {
web_1       |     "enabled": false,
web_1       |     "invoice_data": {
web_1       |       "account_tax_ids": null,
web_1       |       "custom_fields": null,
web_1       |       "description": null,
web_1       |       "footer": null,
web_1       |       "metadata": {
web_1       |       },
web_1       |       "rendering_options": null
web_1       |     }
web_1       |   },
web_1       |   "livemode": false,
web_1       |   "locale": null,
web_1       |   "metadata": {
web_1       |   },
web_1       |   "mode": "payment",
web_1       |   "payment_intent": "pi_3MdCMtF4dCXP6zuk1hWWC4Tq",
web_1       |   "payment_link": null,
web_1       |   "payment_method_collection": "always",
web_1       |   "payment_method_options": {
web_1       |   },
web_1       |   "payment_method_types": [
web_1       |     "card"
web_1       |   ],
web_1       |   "payment_status": "paid",
web_1       |   "phone_number_collection": {
web_1       |     "enabled": false
web_1       |   },
web_1       |   "recovered_from": null,
web_1       |   "setup_intent": null,
web_1       |   "shipping": {
web_1       |     "address": {
web_1       |       "city": "",
web_1       |       "country": "JP",
web_1       |       "line1": "檜原村",
web_1       |       "line2": null,
web_1       |       "postal_code": "190-0200",
web_1       |       "state": "東京都"
web_1       |     },
web_1       |     "name": "yamada taro"
web_1       |   },
web_1       |   "shipping_address_collection": {
web_1       |     "allowed_countries": [
web_1       |       "JP"
web_1       |     ]
web_1       |   },
web_1       |   "shipping_options": [
web_1       |     {
web_1       |       "shipping_amount": 500,
web_1       |       "shipping_rate": "shr_1MdCMUF4dCXP6zukuuyKl0F7"
web_1       |     }
web_1       |   ],
web_1       |   "shipping_rate": "shr_1MdCMUF4dCXP6zukuuyKl0F7",
web_1       |   "status": "complete",
web_1       |   "submit_type": null,
web_1       |   "subscription": null,
web_1       |   "success_url": "http://localhost:8000/orders/success",
web_1       |   "total_details": {
web_1       |     "amount_discount": 0,
web_1       |     "amount_shipping": 500,
web_1       |     "amount_tax": 0
web_1       |   },
web_1       |   "url": null
web_1       | }

しかし、ドキュメントには shipping というキーは存在しませんので、session オブジェクトに shipping_details というキーが存在するのであれば shipping_details が正しいと考えられます。
よって、書籍にも追記しました。

貴重なご指摘ありがとうございました。

今後ともよろしくお願いします!

tatsuru_hisanagatatsuru_hisanaga

決済処理の動作確認をしていますが、問題点が2つ出てきました

1つは、注文画面で注文内容の詳細が表示されないことです。書籍の画像では購入する商品の内容が詳しく分かりますが、自分の環境ではうまくいきません。なぜでしょうか?

2つ目は、購入完了後にカート内の商品が消去されず、在庫数も更新されないことです。
原因が分からず困っています。何かアドバイス頂けないでしょうか😭🙇‍♂️

webhooks_controller.rb
    def create
        payload = request.body.read
        sig_header = request.env['HTTP_STRIPE_SIGNATURE']
        endpoint_secret = Rails.application.credentials.dig(:stripe, :endpoint_secret)
        event = nil

        begin
        event = Stripe::Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
        rescue JSON::ParserError => e
        # Invalid payload
        p e
        status 400
        return
        rescue Stripe::SignatureVerificationError => e
        # Invalid signature
        p e
        status 400
        return
        end

        case event.type
        when 'checkout.session.completed'
        session = event.data.object # sessionの取得
        customer = Customer.find(session.client_reference_id)
        return unless customer # 顧客が存在するかどうか確認

        # トランザクション処理開始
        ApplicationRecord.transaction do
            order = create_order(session) # sessionを元にordersテーブルにデータを挿入
            session_with_expand = Stripe::Checkout::Session.retrieve({ id: session.id, expand: ['line_items'] })
            session_with_expand.line_items.data.each do |line_item|
            create_order_details(order, line_item) # 取り出したline_itemをorder_detailsテーブルに登録
            end
        end
        # トランザクション処理終了
        customer.cart_items.destroy_all # 顧客のカート内商品を全て削除
        redirect_to session.success_url
        end
    end

    private

    def create_order(session)
        Order.create!({
                        customer_id: session.client_reference_id,
                        name: session.shipping.name,
                        postal_code: session.shipping.address.postal_code,
                        prefecture: session.shipping.address.state,
                        address1: session.shipping.address.line1,
                        address2: session.shipping.address.line2,
                        postage: session.shipping_options[0].shipping_amount,
                        billing_amount: session.amount_total,
                        status: 'confirm_payment'
                    })
    end

    def create_order_details(order, line_item)
        product = Stripe::Product.retrieve(line_item.price.product)
        purchased_product = Product.find(product.metadata.product_id)
        raise ActiveRecord::RecordNotFound if purchased_product.nil?

        order_detail = order.order_details.create!({
                                                    product_id: purchased_product.id,
                                                    price: line_item.price.unit_amount,
                                                    quantity: line_item.quantity
                                                })
    end
    purchased_product.update!(stock: (purchased_product.stock - order_detail.quantity)) # 購入された商品の在庫数の更新
config/routes.rb
Rails.application.routes.draw do
  devise_for :admins, controllers: {
    sessions: 'admin/sessions'
  }
  devise_for :customers, controllers: {
    sessions: 'customer/sessions',
    registrations: 'customer/registrations'
  }

  root to: 'pages#home'
  namespace :admin do
    resources :products, only: %i[index show new create edit update]
  end
  scope module: :customer do
    resources :products, only: %i[index show]
    resources :cart_items, only: %i[index create destroy] do
      member do
        patch 'increase'
        patch 'decrease'
      end
    end
    resources :checkouts, only: [:create]
    resources :webhooks, only: [:create]
  end

  get '/up/', to: 'up#index', as: :up
  get '/up/databases', to: 'up#databases', as: :up_databases
  get '/', to: 'pages#home'

end
FarStepFarStep

ご質問頂きありがとうございます!
お待たせしてすみません。
本日中に対応させて頂きますのでもうしばらくお待ちください。

FarStepFarStep

お待たせしました。
まず一つ目のバグから対応させて頂きます。

【注文画面で注文内容の詳細が表示されない件について】
決済画面で注文内容を表示する際には、app/controllers/customer/checkouts_controller.rb create_session メソッド内で Stripe::Checkout::Session.create() を実行しています。
したがって、こちらのコードにバグがある可能性があります。
app/controllers/customer/checkouts_controller.rb のコードを見せていただいてもよろしいでしょうか。

tatsuru_hisanagatatsuru_hisanaga

返信ありがとうございます🙇‍♂️
以下、app/controllers/customer/checkouts_controller.rb のコードです。
よろしくお願いします

checkouts_controller.rb
    class Customer::CheckoutsController < ApplicationController
        before_action :authenticate_customer!
    
        def create
        line_items = current_customer.line_items_checkout
        session = create_session(line_items)
        # Allow redirection to the host that is different to the current host
        redirect_to session.url, allow_other_host: true
        end
    
        private
    
        def create_session(line_items)
        Stripe::Checkout::Session.create(
            client_reference_id: current_customer.id,
            customer_email: current_customer.email,
            mode: 'payment',
            payment_method_types: ['card'],
            line_items:,
            shipping_address_collection: {
            allowed_countries: ['JP']
            },
            shipping_options: [
                {
                    shipping_rate_data: {
                    type: 'fixed_amount',
                    fixed_amount: {
                        amount: 500,
                        currency: 'jpy'
                    },
                    display_name: 'Single rate'
                    }
                }
            ],
            success_url: "#{root_url}orders/success",
            cancel_url: "#{root_url}cart_items"
        )
        end
    end
FarStepFarStep

ありがとうございます!
checkouts_controller.rb の記述は大丈夫そうですね。
あとは、line_items_checkout メソッドの記述について確認したいです。
GitHub に現在のコードを push していただけると助かります!

isoseaisosea

さきほど教材を完走しました!!
これだけ良質な教材が無料で利用できるのは感動です😭

最後に誤植と思われる箇所を記載いたします。

以上です!ありがとうございました!
これからのFarStepさんの活動も応援しています📣

FarStepFarStep

こんにちは。
最後まで本書籍をご覧頂きありがとうございました!

isosea さんには、本書籍に関するご質問・ご指摘をたくさん頂き、私自身も大変勉強になりました。
ありがとうございました。

誤植についてのご指摘もありがとうございます。こういったコメント非常に助かります 😭
先ほど全ての誤植を修正いたしました。

これからもみなさまのお役に立てるよう、努力していきたいと思います。
今後ともよろしくお願いします!

Kakeru AoyamaKakeru Aoyama

素晴らしい教材を作成いただきありがとうございました。
一点だけ、開発時に不満だった点があります。
決済時に、いちいちStripe CLIを起動させなければならない点です。教材を再開する際に何度か起動し忘れていたことがありました。

公式のドキュメントを見ると、Docker imageも配布されていますし、せっかくDockerで環境構築しているので、Stripe CLIもcomposeで起動させるようにしてみました。
https://github.com/kakeruAoyama/ecommerce/commit/22440e44e5380d8900aa8ea87b4f12945a522b1c
教材の改善につながりますと幸いです!

ao0401ao0401

大変有益な書籍で勉強させていただきありがとうございます。

書籍についてご質問です。
購入完了後にカート内の商品が消去されず、在庫数も更新されない状況です。
原因が分からずアドバイスいただけますと幸いです。

webhooks_controller
class Customer::WebhooksController < ApplicationController
    skip_before_action :verify_authenticity_token
  
    def create
      payload = request.body.read
      sig_header = request.env['HTTP_STRIPE_SIGNATURE']
      endpoint_secret = Rails.application.credentials.dig(:stripe, :endpoint_secret)
      event = nil
  
      begin
        event = Stripe::Webhook.construct_event(
          payload, sig_header, endpoint_secret
        )
      rescue JSON::ParserError => e
        # Invalid payload
        p e
        status 400
        return
      rescue Stripe::SignatureVerificationError => e
        # Invalid signature
        p e
        status 400
        return
      end
  
      case event.type
      when 'checkout.session.completed'
        session = event.data.object # sessionの取得
        customer = Customer.find(session.client_reference_id)
        return unless customer # 顧客が存在するかどうか確認
        
        # トランザクション処理開始
        ApplicationRecord.transaction do
          order = create_order(session) # sessionを元にordersテーブルにデータを挿入
          session_with_expand = Stripe::Checkout::Session.retrieve({ id: session.id, expand: ['line_items'] })
          session_with_expand.line_items.data.each do |line_item|
            create_order_details(order, line_item) # 取り出したline_itemをorder_detailsテーブルに登録
          end
        end
        # トランザクション処理終了
        customer.cart_items.destroy_all # 顧客のカート内商品を全て削除
        redirect_to session.success_url
      end
    end
  
    private
  
    def create_order(session)
      Order.create!({
                      customer_id: session.client_reference_id,
                      name: session.shipping.name,
                      postal_code: session.shipping.address.postal_code,
                      prefecture: session.shipping.address.state,
                      address1: session.shipping.address.line1,
                      address2: session.shipping.address.line2,
                      postage: session.shipping_options[0].shipping_amount,
                      billing_amount: session.amount_total,
                      status: 'confirm_payment'
                    })
    end
  
    def create_order_details(order, line_item)
      product = Stripe::Product.retrieve(line_item.price.product)
      purchased_product = Product.find(product.metadata.product_id)
      raise ActiveRecord::RecordNotFound if purchased_product.nil?
   
      order_detail = order.order_details.create!({
                                                   product_id: purchased_product.id,
                                                   price: line_item.price.unit_amount,
                                                   quantity: line_item.quantity
                                                 })
    purchased_product.update!(stock: (purchased_product.stock - order_detail.quantity)) # 購入された商品の在庫数の更新
    end
  end
2023-08-25 23:04:22   --> charge.succeeded [evt_3Nj0caIzldJfiets1GPupOIp]
2023-08-25 23:04:22   --> checkout.session.completed [evt_1Nj0cbIzldJfietspTg1hpax]
2023-08-25 23:04:22   --> payment_intent.succeeded [evt_3Nj0caIzldJfiets13VkF5rE]
2023-08-25 23:04:22   --> payment_intent.created [evt_3Nj0caIzldJfiets1deusljC]
2023-08-25 23:04:22  <--  [204] POST http://localhost:8000/webhooks [evt_3Nj0caIzldJfiets13VkF5rE]
2023-08-25 23:04:23  <--  [204] POST http://localhost:8000/webhooks [evt_3Nj0caIzldJfiets1deusljC]
2023-08-25 23:04:23  <--  [500] POST http://localhost:8000/webhooks [evt_1Nj0cbIzldJfietspTg1hpax]
2023-08-25 23:04:23  <--  [204] POST http://localhost:8000/webhooks [evt_3Nj0caIzldJfiets1GPupOIp]
豆太郎豆太郎

私も同じエラーで悩んでしましたが、
少し前のisoseaさんのコメントを参考に、

webhooks_controller.rbのcreate_orderメソッドの中で、shippingの部分をshipping_detailsに直すことで解決しました。

よければ、試してみてください。

ao0401ao0401

ありがとうございます!
shipping_detailsに書き換えたところ作動しました!

hiratahirata

すみません私も同じところでつまずいているのですが
こちらはwebhocks_controllerの中のOrder.createのshippingでいいのでしょうか?

def create_order(session)
Order.create!({
customer_id: session.client_reference_id,
name: session.shipping_amount.name,
postal_code: session.shipping_amount.address.postal_code,
prefecture: session.shipping_amount.address.state,
address1: session.shipping_amount.address.line1,
address2: session.shipping_amount.address.line2,
postage: session.shipping_options[0].shipping_amount,
billing_amount: session.amount_total,
status: 'confirm_payment'
})
end

FarStepFarStep

ご返信が遅くなり大変申し訳ありません。
こちらのエラーが再現することを私の手元でも確認しました。

エラーの内容は下記の通りです。

  1. web-1 コンテナ(Rails アプリケーション)
NoMethodError (undefined method `shipping' for #<Stripe::Checkout::Session:0x7a44 id=cs_test_b1O2qVafNY1yDIaHlF5YS133UKETBhC9yp73gQdTOhYjKL4fS00IhDIsgD>
  1. localhost:8000/webhooks を Listen しているターミナル上
2024-01-06 12:30:04   --> charge.succeeded [evt_3OVQaEA8rEAY2MYv1EQBw5e8]
2024-01-06 12:30:04  <--  [204] POST http://localhost:8000/webhooks [evt_3OVQaEA8rEAY2MYv1EQBw5e8]
2024-01-06 12:30:04   --> payment_intent.succeeded [evt_3OVQaEA8rEAY2MYv1p6KUsQ2]
2024-01-06 12:30:04  <--  [204] POST http://localhost:8000/webhooks [evt_3OVQaEA8rEAY2MYv1p6KUsQ2]
2024-01-06 12:30:05   --> checkout.session.completed [evt_1OVQaGA8rEAY2MYv7ZqLntcx]
2024-01-06 12:30:05  <--  [500] POST http://localhost:8000/webhooks [evt_1OVQaGA8rEAY2MYv7ZqLntcx]
2024-01-06 12:30:05   --> payment_intent.created [evt_3OVQaEA8rEAY2MYv1gw41eAv]
2024-01-06 12:30:05  <--  [204] POST http://localhost:8000/webhooks [evt_3OVQaEA8rEAY2MYv1gw41eAv]

isosea さんや豆太郎 さんがおっしゃる通り Customer::WebhooksControllercreate_order メソッド内の session.shippingsession.shipping_details に修正することで、このエラーは解消されます。

つまり、下記のように修正してください。

app/controllers/customer/webhooks_controller.rb
  def create_order(session)
    Order.create!({
                    customer_id: session.client_reference_id,
                    name: session.shipping_details.name,
                    postal_code: session.shipping_details.address.postal_code,
                    prefecture: session.shipping_details.address.state,
                    address1: session.shipping_details.address.line1,
                    address2: session.shipping_details.address.line2,
                    postage: session.shipping_options[0].shipping_amount,
                    billing_amount: session.amount_total,
                    status: 'confirm_payment'
                  })
  end

解決しない場合は、再度ご質問して頂けると幸いです。
何卒よろしくお願いします。

copcop

Stripeの勉強をしたいなと思っており、この書籍を見つけて勉強させていただいている最中です。
FarStep様には無料公開への感謝の念を隠しえません。本当にありがとうございます。

1点質問なのですが、現在決済処理を実装する章を読んでいるのですが、冒頭に決済が失敗した場合の処理を今回は省略しているとありますが、この点についての追記のご予定を伺いたいです。
また、こちらは自分でも調査するつもりではありますが、決済が失敗した場合の処理についてStripeのドキュメントがありましたらご教示頂きたいと存じます。

何卒よろしくお願いいたします。

ちょすちょす

大変貴重な教材ありがとうございます!
現在Stripeのセットアップの箇所でわからなくなっておりまして、

$ EDITOR="vi" bin/rails credentials:edit -e development

こちらのコマンドを実行してもviエディタが起動せず、調べて見たらDockerのコンテナ内でvimがインストールしていないのが原因とわかりましたが、vimのインストールの方法がわからなくて教えていただけますと幸いです。

試したこと

apt update
apt install -y vim

こちらのコマンドを実行してみましたが特にインストールされたりせずとなっております。
お手数をおかけいたしますがよろしくお願いいたします。

FarStepFarStep

こんにちは。
丁寧にご質問して頂き誠にありがとうございます。

下記のコマンドを実行してみてください。

  1. root ユーザでコンテナに入る(パッケージをインストールするために root ユーザの権限が必要)
docker-compose run --rm -u root web bash
  1. vim をインストールする
apt-get update
apt-get install vim

ただし、この方法でインストールした vim は、コンテナを再起動するたびに消えてしまいます。
コンテナのビルドプロセス中に vim を永続的にインストールするには、下記のように Dockerfile を編集してください(本書の Chapter 05 にも記載されています)。

  RUN apt-get update \
-  && apt-get install -y --no-install-recommends build-essential curl libpq-dev \
+  && apt-get install -y --no-install-recommends build-essential curl libpq-dev vim \
   && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \
   && apt-get clean \
   && groupadd -g "${GID}" ruby \
   && useradd --create-home --no-log-init -u "${UID}" -g "${GID}" ruby \
   && chown ruby:ruby -R /app

https://zenn.dev/farstep/books/7f169cdc597ada/viewer/5ff63c

解決しない場合は、再度ご質問して頂けると幸いです。
何卒よろしくお願いします。

ちょすちょす

おはようございます!
ご丁寧に教えていただきありがとうございます!
無事に解決できました!!
お忙しい中ご返信ありがとうございました!

引き続きよろしくお願いいたします!!!!

YutaroYutaro

初めまして。
質問してもよろしいでしょうか。

現在Chapter7を終えたところでコミットし、pull requestを作成したところです。
すると、github actionで

./run ci:install-deps

の実行のタイミングに

0s
Run ls /usr/bin/bash
  ls /usr/bin/bash
  ./run ci:install-deps
  shell: /usr/bin/bash -e {0}
/usr/bin/bash
/home/runner/work/_temp/46b71ed3-f20d-4cb5-b6a9-bca64c42e969.sh: line 2: ./run: Permission denied
Error: Process completed with exit code 126.

とエラーが出てしまいました。

https://zenn.dev/seito/articles/4c2b4ab3bad5c3

で調べたところによると、ファイルアクセス権限の問題のようで、ci.ymlにlsコマンドを追加しましたが結局原因がわからない状態です。
/home/runner/work/_temp/46b71ed3-f20d-4cb5-b6a9-bca64c42e969.sh
へのアクセスができていない、ということで認識はあっていますでしょうか。
またお手数ですが、解消方法をご教示いただけますと幸いです。

参考までに、PRへのリンクを掲載いたします。
https://github.com/Yutaro-orange/EC/pull/3/checks

よろしくお願いいたします。