【PHP】Misoca APIを使用して請求書を作成・ダウンロード

PHPにて、Misoca APIと連携して請求書を作成 & ダウンロードした時のメモ

早速ソース

  • misoca-callback.php

今回、$_GETパラメータにて送信する値を受け取っているので、
適宜ここは環境に合わせて調整が必要

<?php
session_start();

const APP_ID = "{MisocaのAPP ID}";
const SECRET = "{MisocaのAPP SECRET}"
// このファイルのパス
const REDIRECT_URL = "{ドメイン}/misoca-callback.php";

// トークン情報保存用
if (!isset($_SESSION['tokens'])) {
    $_SESSION['tokens'] = [
        'access_token' => null,
        'refresh_token' => null,
        'expires_in' => 0,
    ];
}

// アクセストークンが無効な場合
if (empty($_SESSION['tokens']['access_token']) || $_SESSION['tokens']['expires_in'] < time()) {
    // アクセストークン発行
    getAccessToken();
}

// 請求書作成
createInvoice($_SESSION['tokens']['access_token']);

/**
 * 請求書を作成する
 *
 * @param $accessToken
 * @return void
 */
function createInvoice($accessToken) {
    // 請求書情報作成
    $invoiceData = array(
        'subject' => $_GET['subject'] ?? '請求書タイトル',
        'contact_id' => $_GET['contact_id'] ?? 1,
        'issue_date' => isset($_GET['issue_date']) && strtotime($_GET['issue_date']) ? $_GET['issue_date'] : date('Y-m-d'),
        'items' => [],
    );

    // 商品がある場合は追加
    foreach ($_GET['items'] ?? [] as $item) {
        $invoiceData['items'][] = [
            'name' => $item['name'],
            'quantity' => $item['quantity'],
            'unit_price' => $item['price']
        ];
    }

    // 請求書作成
    $ch = curl_init('https://app.misoca.jp/api/v3/invoice');
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Authorization: Bearer $accessToken",
        "Content-Type: application/json",
    ));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($invoiceData));

    $response = curl_exec($ch);
    $response = json_decode($response, true);
    $err = curl_error($ch);
    curl_close($ch);

    if ($err) {
        exit("cURL Error #:" . $err);
    } elseif (empty($response['id'])) {
        echo "エラーが発生しました<br>";
        foreach ($response['reasons'] ?? [] as $reason) {
            echo $reason . "<br>";
        }
    } else {
        $invoiceId = $response['id'];
        // 請求書PDF取得
        $pdfUrl = "https://app.misoca.jp/api/v3/invoice/{$invoiceId}/pdf";
        $ch = curl_init($pdfUrl);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            "Authorization: Bearer $accessToken",
        ));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $pdfContent = curl_exec($ch);
        $err = curl_error($ch);
        curl_close($ch);

        if ($err) {
            exit("cURL Error #:" . $err);
        } else {
            // PDFファイルを保存
            $pdfFilePath = "invoices/invoice_{$invoiceId}.pdf";
            file_put_contents($pdfFilePath, $pdfContent);
            echo "請求書を作成しました。保存先パス: {$pdfFilePath}";

            // ダウンロード
            header('Content-Type: application/pdf');
            header('Content-Disposition: attachment; filename="invoice_' . $invoiceId . '.pdf"');
            header('Content-Length: ' . strlen($pdfContent));
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header('Pragma: public');
            header('Expires: 0');
            echo $pdfContent;
            exit;
        }
    }
}

/**
 * アクセストークンを取得
 *
 * @return mixed|void
 */
function getAccessToken()
{
    $client_id = APP_ID;
    $client_secret = SECRET;
    $redirect_uri = REDIRECT_URL;

    // 認証コードがある場合、アクセストークンを取得
    if (isset($_GET['code'])) {
        $code = $_GET['code'];
        $url = 'https://app.misoca.jp/oauth2/token';

        $data = array(
            'grant_type' => 'authorization_code',
            'code' => $code,
            'redirect_uri' => $redirect_uri,
            'client_id' => $client_id,
            'client_secret' => $client_secret,
        );

        $options = array(
            'http' => array(
                'header' => "Content-Type: application/x-www-form-urlencoded",
                'method' => 'POST',
                'content' => http_build_query($data),
            ),
        );

        $context = stream_context_create($options);
        $result = file_get_contents($url, false, $context);
        $tokens = json_decode($result, true);

        if (!empty($tokens['access_token'])) {
            // セッションに保存
            $_SESSION['tokens'] = [
                'access_token' => $tokens['access_token'],
                'refresh_token' => $tokens['refresh_token'],
                'expires_in' => time() + $tokens['expires_in'],
            ];
        } else {
            exit('Failed to obtain access token');
        }
    // リフレッシュトークンがある場合リフレッシュする
    } elseif (!empty($_SESSION['tokens']['refresh_token']) && $_SESSION['tokens']['expires_in'] < time()) {
        refreshToken();
    } else {
        // ユーザーを認証用URLにリダイレクト
        $scope = 'write';
        $response_type = 'code';
        $auth_url = "https://app.misoca.jp/oauth2/authorize?response_type={$response_type}&client_id={$client_id}&redirect_uri={$redirect_uri}&scope={$scope}";
        header('Location: ' . $auth_url);
        exit;
    }
}

/**
 * トークンをリフレッシュする
 *
 * @return void
 */
function refreshToken()
{
    $refresh_token = $_SESSION['tokens']['refresh_token'];
    $url = 'https://app.misoca.jp/oauth2/token';

    $data = array(
        'grant_type' => 'refresh_token',
        'refresh_token' => $refresh_token,
        'client_id' => APP_ID,
        'client_secret' => SECRET,
    );

    $options = array(
        'http' => array(
            'header' => "Content-Type: application/x-www-form-urlencoded",
            'method' => 'POST',
            'content' => http_build_query($data),
        ),
    );

    $context = stream_context_create($options);
    $result = file_get_contents($url, false, $context);
    $tokens = json_decode($result, true);

    if (!empty($tokens['access_token'])) {
        // 新しいアクセストークンとリフレッシュトークンをセッションに保存
        $_SESSION['tokens'] = [
            'access_token' => $tokens['access_token'],
            'refresh_token' => isset($tokens['refresh_token']) ? $tokens['refresh_token'] : $refresh_token, // 新しいリフレッシュトークンがあれば更新
            'expires_in' => time() + $tokens['expires_in'],
        ];
    } else {
        exit('Failed to refresh access token');
    }
}

\ 案件のご依頼・ご相談はこちらから /