Help us understand the problem. What is going on with this article?

[HowTo]Pay.jpを用いた商品購入機能実装から商品購入後の設定まで

某スクールのチーム開発にてpay.jpを活用したクレジットカード登録と商品購入機能実装を担当させていただくことになりました!
色々と苦戦しましたがなんとか実装できましたので、以下にまとめてみたいと思います。
今回は商品購入機能実装から商品購入後の設定までまとめております!
カスタムフォームを使用したクレジットカード登録については、以下の過去記事をご参照いただけますと幸いです。
https://qiita.com/Tatsu88/items/23fe4b83d0ff8d78709d

実装内容

購入イメージ
Payjpのアカウント情報を取得して、商品の購入を行ってます。
demo

購入後イメージ
購入後はその製品の写真にSoldとつけ、さらに購入ボタンを使用できないようにしています。
demo

全体の流れ

  1. [購入]コントローラの編集
  2. [購入後]viewの編集
  3. ルーティングの設定
  4. 注意点(公開鍵と秘密鍵について)

1. [購入]コントローラの編集

今回、実装するページ遷移としては以下のようになってます。
商品一覧ページ=>商品詳細ページ=>商品購入確認ページ=>商品購入(コントローラのみ)=>購入完了ページ
*商品一覧ページと商品詳細ページと購入完了ページは既にあるものとして割愛させていただきます。

商品購入確認ページの実装

それでは、商品購入確認ページから実装していきます。
こちらの内容に関しては、以下をポイントとしてコントローラを設定します。
1. 商品/ユーザー/クレジットカードの変数設定
2. Payjpの秘密鍵を取得
3. Payjpから顧客情報を取得し、表示

controller
  def buy
 #商品/ユーザー/クレジットカードの変数設定
    @user = current_user
    @creditcard = Creditcard.where(user_id: current_user.id).first
    @address = Address.where(user_id: current_user.id).first
    @product = Product.find(params[:id])
  #Payjpの秘密鍵を取得
    Payjp.api_key = ”秘密鍵”
  #Payjpから顧客情報を取得し、表示
    customer = Payjp::Customer.retrieve(@creditcard.customer_id)
    @creditcard_information = customer.cards.retrieve(@creditcard.card_id)
    @card_brand = @creditcard_information.brand 
    case @card_brand
    when "Visa"
      @card_src = "visa.svg"
    when "JCB"
      @card_src = "jcb.svg"
    when "MasterCard"
      @card_src = "master-card.svg"
    when "American Express"
      @card_src = "american_express.svg"
    when "Diners Club"
      @card_src = "dinersclub.svg"
    when "Discover"
      @card_src = "discover.svg"
    end
  end

Viewは以下のように実装しております。(参考まで)

view
=render 'single-container'

%main.buy-main 
  = form_tag(action: :purchase, method: :post) do
    .buy-item-container
      %h2.buy-item-head 購入内容の確認
      %section.buy-content.buy-item
        .buy-content-inner
          .buy-item-box
            .buy-item-image
              =image_tag(@product.images[0].product_image.url,class:"buy-image")
            .buy-item-detail
              %p.buy-item-name
                =@product.name
                %p.buy-item-price.bold
                  = number_to_currency(@product.price,format: "%u%n",unit:"¥",precision: 0)
                  %span.item-shipping-fee.f14.bold
                    (税込)送料込み
      %section.buy-content.buy-user-info
        .buy-content-inner
          %ul.buy-price-table
            %li.buy-price-row.buy-you-pay.bold
            %li.buy-price-cell 支払い金額
            %li.buy-price-cell
              = number_to_currency(@product.price,format: "%u%n",unit:"¥",precision: 0)
          %section.buy-content.buy-user-info
        .buy-content-inner
          %h3 支払方法
          .payment-content__creditcards__list
            %figure
              = image_tag "#{@card_src}",alt: @card_brand, id: "card_image"
            .payment-content__creditcards__list__number
              = "**** **** **** " + @creditcard_information.last4
            .payment-content__creditcards__list__number
              - exp_month = @creditcard_information.exp_month.to_s
              - exp_year = @creditcard_information.exp_year.to_s.slice(2,3)
              = exp_month + " / " + exp_year
          %section.buy-content.buy-user-info
        .buy-content-inner
          %h3 配送先
          %address.buy-user-info-text
            =@address.postal_code
            %br
            =@address.prefecture
            %br
            =@address.city
            =@address.address
            =@address.apartment
            %br
            =@user.last_name
            =@user.first_name
          %section.buy-content.buy-user-info
          = submit_tag("購入する", class:"purchase")
.exhibit-page__footer
  .exhibit-page__footer__content
    .exhibit-page__footer__content__main
      %ul.exhibit-page__footer__lists
        %li.exhibit-page__footer__list
          プライバシーポリシー
        %li.exhibit-page__footer__list
          メルカリ利用規約
        %li.exhibit-page__footer__list
          特定商取引に関する表記
        %p.exhibit-page__footer__copyright
          © 2019 Mercari

商品購入(コントローラのみ)

上記で実装した商品購入確認ページ情報の購入するを押した時に動くメソッドをコントローラに記載します。
ここでのポイントは以下の通りです。
1. クレジットカードと製品の変数を設定
2. Payjpの秘密鍵を取得
3. payjp経由で支払いを実行
4. 製品のbuyer_idを付与(このbuyer_id、現状は不要ですが、以降の記述で使用します)

支払いについては、下記リファレンスをご参照ください。
https://pay.jp/docs/api/#%E6%94%AF%E6%89%95%E3%81%84%E3%82%92%E4%BD%9C%E6%88%90

controller
  def purchase
 #クレジットカードと製品の変数を設定
    @creditcard = Creditcard.where(user_id: current_user.id).first
    @product = Product.find(params[:id])
 #Payjpの秘密鍵を取得
    Payjp.api_key= '秘密鍵'
 #payjp経由で支払いを実行
    charge = Payjp::Charge.create(
      amount: @product.price,
      customer: Payjp::Customer.retrieve(@creditcard.customer_id),
      currency: 'jpy'
    )
 #製品のbuyer_idを付与
    @product_buyer= Product.find(params[:id])
    @product_buyer.update( buyer_id: current_user.id)
    redirect_to purchased_product_path
  end

以上で商品の購入ができました。
Payjpでログインし、売り上げにしっかりと反映されているか確認しましょう。

続いて購入後のviewの編集を行います。

2. [購入後]viewの編集

購入された製品がいつまで経っても一覧に残っていたり、また購入できたりすると問題です。
そのような事態を防ぐために以下の記述を行いましょう。

商品一覧ページの編集

まず、商品一覧ページのサムネイルに”SOLD”の画像を追加しましょう。
ここでのポイントは以下の通りです。
1. 製品に関して、"buyer_id"の有無を確認
2. "buyer_id"がある場合には、追加の記述を行う

view*一部抜粋
  %li.products__list
    %figure.product
      %figucaption.product__text
        = product.name
      .product__thumbnail
        .product__thumbnail--label
          = #{product.price.to_s(:delimited)}"
        .product__thumbnail--image
          =image_tag(product.images[0].product_image.url)
 #製品に関して、"buyer_id"の有無を確認
            -if product.buyer_id.present? 
 #"buyer_id"がある場合には、追加の記述を行う。
            .items-box_photo__sold
              .items-box_photo__sold__inner SOLD

追加されるクラスのCSSは以下のようになってます。
ポイントとしてSOLDの赤三角形を左上に表示するのですが、その部分はborderを使って実装してます。
width: 0;とheight: 0;にして、border-widthは上と右側のみを幅を設定し、
border-colorは上部のみに設定しています。

css*一部抜粋
.items-box_photo__sold{
  width: 0;
  height: 0;
  border-width: 120px 120px 0 0;
  border-color: #ea352d transparent transparent transparent;
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  border-style: solid;
  &__inner{
    top: -90px;
    font-size: 30px;
    position: absolute;
    left: 0;
    z-index: 2;
    color: #fff;
    transform: rotate(-45deg);
    letter-spacing: 2px;
    font-weight: 600;
  }
}

商品購入確認ページの編集

続いて商品購入確認ページも編集します。
既に購入された製品は購入できないようにしましょう。
*写真に関しては、上記と同様のため割愛します。

ここでのポイントは以下の通りです。
1. 製品に関して、"buyer_id"の有無を確認
2. "buyer_id"がある場合には、”売り切れました”と表示する。(ボタンもdisableにします)

view*一部抜粋
        .product-buy__btn__box       
        - if user_signed_in? && current_user.id ==@product.user_id
          = link_to "削除する", product_path(@product.id), method: :delete,class:"product-details-delete__btn"
          = link_to "編集する", edit_product_path(@product.id),class:"product-details-edit__btn"
 #製品に関して、"buyer_id"の有無を確認
        - elsif @product.buyer_id.present? 
          = link_to "売り切れました",buy_product_path,class:"disabled-button bold"
        - else
          = link_to "購入画面に進む",buy_product_path,class:"product-purchase__btn"

追加されるクラスのCSSは以下のようになってます。

css*一部抜粋
.disabled-button{
  text-align: center;
  margin: 16px 0 0;
  background: #888888;
  color: #fff;
  display: block;
  width: 100%;
  font-size: 24px;
  line-height: 60px;
  cursor: not-allowed;
}

以上で購入後に行うべき変更が実装できました。

3. ルーティングの設定

上記にてコントローラとviewの設定が完了しました!
最後にルーティングを設定しましょう。
注意点としては、memberを使用することにあります。
こちらをcollectionにしてしまうと、productのidを取得できなくなってしまいますので、
idを取得するためにmemberで設定しましょう。(私はここで迷走しました笑)

route.rb*一部抜粋
resources :products do
    member do
      post 'purchase'
      get 'purchased'
      get 'buy'
    end
end

4. 注意点(公開鍵と秘密鍵について)

上記にて機能の実装ができました。
そこで一点注意がございます。
公開鍵と秘密鍵をそのまま記述してしまうのは危険なため、secrets.ymlなどに記述しましょう。

公開鍵と秘密鍵を.bash_profileに格納

まず、公開鍵と秘密鍵を隠しファイルに格納します。

ターミナル
#公開鍵と秘密鍵を記述します。
$ vim ~/.bash_profile

#まず「i」を押して入力モードにしましょう
export PAYJP_ACCESS_KEY='sk_test_*************'
export PAYJP_PUBLIC_KEY='pk_test_*************'
#入力後、"escキー"=> ":" => "w" => "q"の順でコマンドを打ちましょう。

#公開鍵と秘密鍵を記述後、保存を行います。
$ source ~/.bash_profile

Vimのコマンド詳細は以下を確認ください
https://qiita.com/hide/items/5bfe5b322872c61a6896

以上で隠しファイルへの記述は完了です。
続いてsecrets.ymlに記述しましょう。

secrets.ymlに記載

今回、rubyのver的にsecrets.ymlに記載しております。
verが新しい方はcredential.ymlに記載ください。

secrets.yml
#developmentとproduction、共に記載します。
  payjp_access_key: <%= ENV["PAYJP_ACCESS_KEY"] %>
  payjp_public_key: <%= ENV["PAYJP_PUBLIC_KEY"] %>

各コントローラへの記述をベタ打ちから変更

上記にてやっと準備が整いました。
コントローラへの記述をベタ打ちから変更しましょう。

contoroller
#どちらでも機能します。どちらかを記述ください
Payjp.api_key =ENV["PAYJP_ACCESS_KEY"]

Payjp.api_key = Rails.application.secrets.payjp_access_key

参照

PAYjp 支払いを行う
https://pay.jp/docs/charge

Payjpに登録したクレジットカードで商品購入を実装する(Rails)
https://qiita.com/takachan_coding/items/d21c0d2621368c9b0d9b

RailsでPayjpを使った購入機能を実装する
https://qiita.com/suzy1031/items/7964829086eb929471a6

商品購入後の商品にSOLDを表記(某フリマアプリのSOLD機能)
https://qiita.com/kortaban/items/ddc613b330c19fcedbeb

以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした