【Laravel / PHP】ロジレスAPI連携

えび

LaravelロジレスAPI連携実装した時のメモ
素のPHPでも少しいじれば使えるはず

流れ

  1. API通信パッケージのGuzzle入れる
  2. ロジレスへのアクセス情報 (URLやトークン) を設定する
  3. アクセストークンリフレッシュトークンを取得してデータベースに入れる
  4. トークンを利用してAPIにリクエストを投げる

Guzzleいれる

composer require guzzlehttp/guzzle

.envにロジレスアクセス情報を記載

LOGILESS_AUTH_ENDPOINT=https://app2.logiless.com/oauth/v2/auth
LOGILESS_TOKEN_ENDPOINT=https://app2.logiless.com/oauth2/token
LOGILESS_ENDPOINT=https://app2.logiless.com/api
CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXX
CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXX
MERCHANT_ID=XXX

CLIENT_ID (クライアントID)とCLIENT_SECRET (クライアントシークレット)は
管理画面のアプリケーション詳細から取得可能 (こちら参照)

config/logiless.phpを作成

<?php
return [
    'endpoint' => [
        'auth' => env('LOGILESS_AUTH_ENDPOINT'),
        'token' => env('LOGILESS_TOKEN_ENDPOINT'),
        'access' => env('LOGILESS_ENDPOINT'),
    ],
    'auth' => [
        'client_id' => env('CLIENT_ID'),
        'client_secret' => env('CLIENT_SECRET'),
        'merchant_id' => env('MERCHANT_ID'),
    ],
];

ロジレストークンを格納するテーブル & モデルを作成

php artisan make:model LogilessToken --migration
  • マイグレーション
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateLogilessTokenTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('logiless_token', function (Blueprint $table) {
            $table->id();
            $table->string('access_token')->nullable()->comment('アクセストークン');
            $table->string('refresh_token')->nullable()->comment('リフレッシュトークン');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('logiless_token');
    }
}
  • モデル
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class LogilessToken extends Model
{
    protected $table = 'logiless_token';

    /**
     * @var array
     */
    protected $guarded = ['id'];
}

アクセストークンリフレッシュトークンを取得する

認証に関する仕様はこちら

直接、認証URLにGETアクセスしてトークンをコピペ & データベースに貼り付けでもいいが、開発用ユーザーではなく顧客ユーザーでトークンを取得したいケースなどがあるのでサイト上で発行できるようにする

更新画面とか作るのはめんどくチャイので、今回は/create-tokenというURLにGETアクセスした場合に自動で新規発行 or 更新がされるように実装

  • routes/web.php
/**
 * トークン作成
 */
Route::get('/create-token', 'CreateTokenController@index');
Route::get('/create-token-complete', 'CreateTokenController@complete');
  • CreateTokenController.php
<?php

namespace App\Http\Controllers;

use App\LogilessToken;
use App\Services\LogilessService;
use Illuminate\Http\Request;

class CreateTokenController extends Controller
{
    /**
     * @var LogilessToken
     */
    private $logilessToken;

    public function __construct()
    {
        $this->logilessToken = LogilessToken::firstOrCreate([]);
    }

    /**
     * トークン作成
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function index(Request $request)
    {
        $logilessService = new LogilessService();

        // パラメータにcodeがない場合
        if (empty($code = $request->get('code'))) {
            $url = config('logiless.endpoint.auth');
            $client_id = config('logiless.auth.client_id');
            $redirect_uri = route('create.token');
            // ロジレス認証へリダイレクト
            return redirect()->away($url .
                "?response_type=code&client_id=$client_id&redirect_uri=$redirect_uri");
        }

        // パラメータのcodeを使用してロジレストークンを取得
        $token = $logilessService->getToken($code);
        // DBのトークンレコード情報更新
        $this->logilessToken->fill([
            'access_token' => $token['access_token'],
            'refresh_token' => $token['refresh_token'],
        ])->save();

        return redirect('/create-token-complete');
    }

    /**
     * トークン作成完了画面
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function complete()
    {
        return view('complete-create-token');
    }
}
  • resources/views/complete-create-token.blade.php
@extends('layouts.app')

@section('content')
      <div class="alert alert-success alert-dismissible" role="alert">
        アクセストークン更新が完了しました。
      </div>
@endsection

これでlogiless_tokenテーブルにトークンが格納できたので、最後にサービスの実装に進む

サービスクラスを作成する

  • app/Services/LogilessService.php
<?php

namespace App\Services;

use App\LogilessToken;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;

/**
 * ロジレス連携用サービス
 * @package App\Services
 */
class LogilessService
{
    /**
     * @var LogilessToken
     */
    private $logilessToken;
    /**
     * @var array
     */
    private $headers;
    /**
     * @var Client
     */
    private $client;
    /**
     * @var string
     */
    private $tokenEndpoint;
    /**
     * @var string
     */
    private $accessEndpoint;
    /**
     * @var string
     */
    private $clientId;
    /**
     * @var string
     */
    private $clientSecret;
    /**
     * @var string
     */
    private $merchant_id;

    public function __construct()
    {
        $logilessToken = LogilessToken::first();
        if (empty($logilessToken)) {
            Log::error('ロジレストークンレコードが存在しません。');
            abort(500);
        }

        $this->logilessToken = $logilessToken;
        $this->headers = [
            'Authorization' => 'Bearer ' . $logilessToken->access_token,
        ];
        $this->client = new Client();
        $this->tokenEndpoint = config('logiless.endpoint.token');
        $this->accessEndpoint = config('logiless.endpoint.access');
        $this->clientId = config('logiless.auth.client_id');
        $this->clientSecret = config('logiless.auth.client_secret');
        $this->merchant_id = config('logiless.auth.merchant_id');
    }

    /**
     * APIにリクエストを投げる
     *
     * @param string $url
     * @param string $method
     * @param array $options
     * @param bool $isRetry
     * @return \Exception|GuzzleException|\Psr\Http\Message\ResponseInterface
     * @throws GuzzleException
     */
    public function request(string $url, string $method, $options = [], $isRetry = false)
    {
        try {
            return $this->client->request($method,
                $url, $options
            );

        } catch (GuzzleException $e) {
            /**
             * 認証エラーかつリトライではない場合
             * リフレッシュトークンを使用してトークン更新 & リトライ
             */
            if ($e->getCode() === 401 && !$isRetry) {
                // リフレッシュトークンで更新
                $response = $this->requestRefreshToken();
                $token = json_decode($response->getBody(), true);
                // DBのトークンレコード情報更新
                $this->logilessToken->update([
                    'access_token' => $token['access_token'],
                    'refresh_token' => $token['refresh_token']
                ]);
                // トークンを更新してリトライ
                $options['headers'] = [
                    'Authorization' => 'Bearer ' . $token['access_token'],
                ];
                return $this->retryRequest($url, $method, $options);

            } else {
                Log::error($e->getMessage());
                abort(500);
            }
        }
    }

    /**
     * リクエストをリトライする
     *
     * @param string $url
     * @param string $method
     * @param array $options
     * @return \Exception|GuzzleException|\Psr\Http\Message\ResponseInterface
     * @throws GuzzleException
     */
    public function retryRequest(string $url, string $method, $options = [])
    {
        // 無限ループを避けるため、$isRetryはtrueを入れる
        return $this->request($url, $method, $options, true);
    }

    /**
     * トークンリセットのリクエストを投げる
     *
     * @return \Psr\Http\Message\ResponseInterface
     * @throws GuzzleException
     */
    public function requestRefreshToken()
    {
        try {
            return $this->client->request('GET',
                $this->tokenEndpoint, [
                    'query' => [
                        'client_id' => $this->clientId,
                        'client_secret' => $this->clientSecret,
                        'refresh_token' => $this->logilessToken->refresh_token,
                        'grant_type' => 'refresh_token',
                    ]
                ]
            );

        } catch (GuzzleException $e) {
            Log::error($e->getMessage());
            abort(500);
        }
    }

    /**
     * 認証コードからロジレストークンを取得する
     *
     * @param string $code
     * @return mixed|\Psr\Http\Message\StreamInterface
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function getToken(string $code)
    {
        try {
            $redirect_uri = url('/create-token');
            $response = $this->request(
                "$this->tokenEndpoint?client_id=$this->clientId&client_secret=$this->clientSecret&grant_type=authorization_code&redirect_uri=$redirect_uri&code=$code",
                'GET'
            );
            $token = $response->getBody();
            $token = json_decode($token, true);

            return $token;

        } catch (GuzzleException $e) {
            Log::error($e->getMessage());
            abort(500);
        }
    }

    /**
     * 受注一覧を取得する
     *
     * @param array $query
     * @return mixed
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function getSalesOrders($query = [])
    {
        try {
            $response =
                $this->request(
                    "$this->accessEndpoint/v1/merchant/$this->merchant_id/sales_orders",
                    'GET',
                    [
                        'headers' => $this->headers,
                        'query' => $query
                    ]
                );

            $responseData = $response->getBody();
            $responseData = json_decode($responseData, true);

            return $responseData['data'];

        } catch (GuzzleException $e) {
            Log::error($e->getMessage());
            abort(500);
        }
    }
}

ざっくり解説
  • ここでは、受注一覧を取得する場合のメソッドのみ記載しているが、他の情報も下記のURLのsales_orders部分を変更すれば取得可能なので専用メソッドを増やすなりすればOK
$response =
  $this->request(
    "$this->accessEndpoint/v1/merchant/$this->merchant_id/sales_orders",
    'GET',
    [
      'headers' => $this->headers,
      'query' => $query
     ]
  );

各リクエストのエンドポイントドキュメントで確認できる

  • アクセストークンの有効期間が過ぎている場合エラーになることがある。
    そのため、requestメソッドで401エラーが発生した場合、1度だけリフレッシュトークンを使用してアクセストークンを再取得する。
    それでもまだエラーが発生する場合は500エラーとする。

Controllerで使う場合

こんな感じで動くはず

  • SalesOrderController.php
// 省略
use App\Services\LogilessService;

// 省略

public function get(Request $request)
{
  $logilessService = new LogilessService();
  $salesOrders = $logilessService->getSalesOrders();
}
  • ページ数や条件指定したい場合
public function get(Request $request)
{
  $logilessService = new LogilessService();
  $salesOrders = $logilessService->getSalesOrders([
      'limit' => 100,
      'page' => 2,
      'document_status' => 'WaitingForShipment,Shipped',
  ]);
}

検索パラメータの種類に関しては各エンドポイントパラメーター参照