【Laravel5.8+Stripe⑥】Laravel CasherとStripeを導入して管理者権限を設定する その2

f:id:nekorokkekun:20190827102831p:plain:w1000
こちらの連載記事では、LaravelとStripeを使用して企業サイト兼Eコマース(ECサイト)を作成していきます。

Laravelのプロジェクト作成からStripeの実装まで行い、最終的に単発決済・サブスクリプションの実装までを目指します。

シリーズ

【Laravel5.8+Stripe①】ベースプロジェクトの作成 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe②】メールフォームの実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe③】ユーザー認証機能のカスタマイズ - Laravelとねころっけくん5.8
【Laravel5.8+Stripe④】ディスカウントページを作成する - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑤】Laravel CasherとStripeを導入して管理者権限を設定する その1 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑥】Laravel CasherとStripeを導入して管理者権限を設定する その2 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑦】サブスクリプション決済の作成 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑧】請求書ダウンロード機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑨】サブスクリプションプラン変更機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑩】サブスクリプション中止機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe11】Webhookの実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe12】クーポン機能を実装する - Laravelとねころっけくん5.8
【Laravel5.8+Stripe13】ショッピングカートの実装 - Laravelとねころっけくん5.8


ちなみにこちらの記事は、Easy E-Commerce Using Laravel and Stripeという書籍をもとに執筆しています。


今回はStripe Checkoutの決済機能を導入していきます。

StripeとLaravel Casherに関する説明

Stripeはインターネットで商品を購入する際のクレジットカード情報などを安全に送信するための仕組みを提供しています。

UI的にはモーダルウィンドウで決済情報の入力をするだけでよく、ユーザーの使用感が高いことが特徴です。

クレジットカード情報がStripeサーバに送られると、Stripeサーバは一意のトークン(stripeToken)を返します。

次にこのトークンとともに、購入情報などに関して、Laravel Casher経由でStripe APIへ送信します。

Stripeサービスは、情報を受信すると、トークンを使用して顧客のクレジットカードを取得し、購入プロセスを完了します。

以下に一般的な決済で使用されるStripeのソースコード見てみましょう。

{!! Form::open(array('url' => '/checkout')) !!}
{!! Form::hidden('product_id', $product->id) !!}
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{env('STRIPE_API_PUBLIC')}}"
data-name="WeDewLawns.com"
data-billingAddress=true
data-shippingAddress=true
data-label="Buy ${{ $product->price }}"
data-description="{{ $product->name }}"
data-amount="{{ $product->priceToCents() }}">
</script>
{!! Form::close() !!}

class

class属性をstripe-buttonに設定すると、デフォルトの青いテーマを使用して[購入]ボタンが様式化されます。ボタンのスタイルをカスタマイズすることが可能

data-key

data-key属性は、公開可能なAPIキーをStripeに渡すために使用されます。

data-name

data-name属性は、Webサイトまたは会社の名前を識別します。
こちらで設定した値が、そのままStripeのモーダルに使用されます。

data-billingAddress

data-billingAddressがtrueに設定されている場合、Stripe Checkoutのモーダルでは、ユーザーに請求先住所の入力が要求されます。

data-shippingAddress

data-shippingAddressがtrueに設定されている場合、Stripe Checkoutのモーダルでは、ユーザーに配送先住所の指定を要求します。これは顧客が物理的な製品を購入するときに役立ちます。

data-label

data-labelフィールドには、[購入]ボタンで使用されるテキストが含まれます。

data-description

data-descriptionフィールドには、チェックアウトモーダルの会社名またはWebサイト名の下にあるテキストが含まれています。

data-amount

data-amountフィールドには、ユーザーのクレジットカードに請求される金額が含まれます。この値は、支払い送信ボタンに表示されます。

ちなみにこちらで使用されているpriceToCentというメソッドは以下のような仕組みになっています。

public function priceToCents()
{
    return $this->price * 100;
}

選択された商品のpriceカラムから値を取得し、それに100を掛けています。
これは例えば「3.99」ドルという数値を「399」セントと表示し直すためのものです。

Buyボタンを押された後に送信される値は?

先ほどStripeの流れとして

クレジットカード情報がStripeサーバに送られると、Stripeサーバは一意のトークン(stripeToken)を返します。

次にこのトークンとともに、購入情報などに関して、Laravel Casher経由でStripe APIへ送信します。

といったことを書きました。

それではLaravel Casherを経由する購入情報とはどのようなものなのでしょうか。
一例ですが、以下のような情報がフォームに埋め込まれるようになります。

<input type="hidden" name="stripeToken" value="tok_15rtKuBJbKzGetVOPKP">
<input type="hidden" name="stripeTokenType" value="card">
<input type="hidden" name="stripeEmail" value="wjgilmore@example.com">
<input type="hidden" name="stripeBillingName" value="Jason Gilmore">
<input type="hidden" name="stripeBillingAddressLine1" value="12 Jump Ct.">
<input type="hidden" name="stripeBillingAddressZip" value="43016">
<input type="hidden" name="stripeBillingAddressState" value="OH">
<input type="hidden" name="stripeBillingAddressCity" value="Dublin">
<input type="hidden" name="stripeBillingAddressCountry" 
  value="United States">
<input type="hidden" name="stripeBillingAddressCountryCode" value="US">
<input type="hidden" name="stripeShippingName" value="Jason Gilmore">
<input type="hidden" name="stripeShippingAddressLine1" value="12 Jump Ct.">
<input type="hidden" name="stripeShippingAddressZip" value="43016">
<input type="hidden" name="stripeShippingAddressState" value="OH">
<input type="hidden" name="stripeShippingAddressCity" value="Dublin">
<input type="hidden" name="stripeShippingAddressCountry" 
  value="United States">
<input type="hidden" name="stripeShippingAddressCountryCode" value="US">

注文管理モデルとテーブルの作成

まずは顧客からの注文を管理するモデルとテーブルを作成していきましょう。

$ php artisan make:model Order -m

マイグレーションファイルにカラムを追加します。
/database/migrations/2019_08_17_221440_create_orders_table.php

            $table->increments('id');
            $table->string('order_number');
            $table->string('email');
            $table->string('billing_name');
            $table->string('billing_address');
            $table->string('billing_city');
            $table->string('billing_state');
            $table->string('billing_zip');
            $table->string('billing_country');
            $table->string('shipping_name')->nullable();
            $table->string('shipping_address')->nullable();
            $table->string('shipping_city')->nullable();
            $table->string('shipping_state')->nullable();
            $table->string('shipping_zip')->nullable();
            $table->string('shipping_country')->nullable();
            $table->string('onetimeurl')->nullable();
            $table->timestamps();

onetimeurlカラムは、ダウンロード製品の購入と組み合わせて使用​​します。購入が完了すると、「1回限り」の使用を目的とした一意のURLが生成され、このカラムに保存されます。顧客には、このURLを含む電子メールが送信されます。顧客がリンクをクリックすると、ダウンロードプロセスが開始されます。


また、ptoductテーブルの主キーであるidカラムのデータ型を変更する必要もあります。

/database/migrations/2019_08_17_014731_create_products_table.php

    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->increments('id'); // bigIncrementsから変更
            $table->string('sku')->unique();
            $table->string('name');
            $table->text('description');
            $table->decimal('price', 6, 2);
            $table->boolean('is_downloadble')->default(false);
            $table->timestamps();
        });
    }

ProductモデルとOrderモデルの間のリレーションを作成していきます。

/app/Product.php

class Product extends Model
{
    public function orders(){
        return $this->hasMany('App\Order');
    }
}

/app/Order.php

class Order extends Model
{
    public function product(){
        return $this->belongsTo('App\Product');
    }
}

SoftDeletesの追加

SoftDeletesとは、指定したテーブルカラムにdeleted_atを追加し、レコードを論理削除できる仕組みです。このSoftDeletesを行うと、deleted_atにタイムスタンプが押されるだけで、レコードが削除されません。しかし、通常のselect文では検索ができないようになります。

SoftDeletesは、特にECサイトなどで使用できるでしょう。なぜなら、商品が生産されなくなった場合、商品自体はサイトから非表示にしたとしても過去の注文自体は消去したくないケースが多いからです。

こちらではproductsテーブルにSoftDeletesを追加していきます。

/app/Product.php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Product extends Model
{
    use SoftDeletes;
    
    protected $dates = ['deleted_at'];
    
    public function orders(){
        return $this->hasMany('App\Order');
    }
}

次にdeleted_atカラムを追加するためのmigrationファイルを作成していきます。

$ php artisan make:migration add_deleted_at_to_products_table --table=products

/database/migrations/2019_08_17_224652_add_deleted_at_to_products_table.php

    public function up()
    {
        Schema::table('products', function (Blueprint $table) {
            $table->softDeletes();
        });
    }

migrationを実行します。

$ php artisan migrate
Migrating: 2019_08_17_224652_add_deleted_at_to_products_table
Migrated:  2019_08_17_224652_add_deleted_at_to_products_table (0.01 seconds)

次にorderを確認するための機能を実装していきましょう。

まずはルーティングからです。
/routes/web.php

Route::group(['prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => 'admin'], function()
{
    Route::resource('products', 'ProductController');
    Route::get('orders', 'ProductController@orders'); // 追加
});

次にControllerです。
/app/Http/Controllers/Admin/ProductController.php

    public function orders() {
        $orders = Order::all();
        return view('admin/orders',compact('orders'));
    }

最後にViewを作成しましょう。
/resources/views/admin/orders.blade.php

@extends('layouts.app')
@section('content')
<h2>Admin Orders Page</h2>
<table class="table">
  <thead class="thead-dark">
    <tr>
      <th scope="col">order_number</th>
      <th scope="col">email</th>
      <th scope="col">billing_name</th>
      <th scope="col">billing_address</th>
      <th scope="col">billing_city</th>
      <th scope="col">billing_state</th>
      <th scope="col">billing_zip</th>
      <th scope="col">billing_country</th>
      <th scope="col">order date</th>
    </tr>
  </thead>
  <tbody>
    @foreach($orders as $order)
    <tr>
      <td>{{$order->order_number}}</td>
      <td>{{$order->email}}</td>
      <td>{{$order->billing_name}}</td>
      <td>{{$order->billing_address}}</td>
      <td>{{$order->billing_city}}</td>
      <td>{{$order->billing_state}}</td>
      <td>{{$order->billing_zip}}</td>
      <td>{{$order->billing_country}}</td>
      <td>{{$order->created_at}}</td>
    </tr>
    @endforeach
  </tbody>
</table>
  {!! Form::close() !!}
@endsection

admin/ordersにアクセスするには、usersテーブルのis_adminカラムがtrueになっていないといけないので、MySQL上から下記のSQL文を入力します。

update users set is_admin=true where id=1;

これで下記のようなViewが表示されるはずです。
f:id:nekorokkekun:20190827094404p:plain

orders自体は、Stripe Checkoutを使用した後半の記事で触れるのでまだレコード自体は挿入されていなくても問題ありません。

余談

Stripe CheckOutを使用した際に、一例ですが以下のような配列型データを送信します。

 1 array:18 [
 2   "_token" => "ISH5o3abrUxleE6y4MIvOrHjoQVQFZ7LirzaKw9a"
 3   "stripeToken" => "tok_15pOyvBJbKzGteVOZH3g10ob"
 4   "stripeTokenType" => "card"
 5   "stripeEmail" => "wjgilmore@example.com"
 6   "stripeBillingName" => "Jason Gilmore"
 7   "stripeBillingAddressLine1" => "1234 Jump Street"
 8   "stripeBillingAddressZip" => "43016"
 9   "stripeBillingAddressState" => "OH"
10   "stripeBillingAddressCity" => "Dublin"
11   "stripeBillingAddressCountry" => "United States"
12   "stripeBillingAddressCountryCode" => "US"
13   "stripeShippingName" => "Jason Gilmore"
14   "stripeShippingAddressLine1" => "1234 Jump Street"
15   "stripeShippingAddressZip" => "43016"
16   "stripeShippingAddressState" => "OH"
17   "stripeShippingAddressCity" => "Dublin"
18   "stripeShippingAddressCountry" => "United States"
19   "stripeShippingAddressCountryCode" => "US"
20 ]

中でもstripeTokenなどは、Controllerで使用することが多いので、配列型データへのアクセス方法を知っておくと便利でしょう。

アクセス方法は簡単で、

$stripeToken = $request->input('stripeToken');

上記のようなコードを書くことでアクセスが可能となっています。