EC-CUBE 2系:MYページに「御請求書」PDF出力機能を追加する

はじめに

EC-CUBE 2系には標準で管理者向けの帳票出力機能(納品書PDF)が搭載されています。
しかし、顧客が自分で請求書を取得できる機能はありません

今回は MYページの「購入履歴詳細」画面に「請求書」ボタンを追加し、
ログイン中のユーザーが自分の注文に対して御請求書PDFをブラウザで直接開けるようにします。


完成イメージ

購入履歴詳細ページの「再注文」ボタンの横に「請求書」ボタンが表示され、
クリックすると新しいタブで御請求書PDFが開きます。

[ 再注文 ]  [ 請求書 ]

既存機能の確認

EC-CUBE 2系には管理者向けに以下の帳票出力機能が実装されています。

/shopman/order/pdf.php?order_id=XXXX

内部では SC_Fpdf クラス(FPDFベース)を使ってPDFを生成します。
今回はこの仕組みをそのまま流用し、ユーザー向けのエントリーポイントを新設します。


実装方針

項目 方針
入力フォーム 不要(管理者と違い、全項目固定値)
発行日 注文日(create_date)を自動セット
タイトル 御請求書(固定)
PDF出力先 ブラウザに直接表示(ダウンロードなし)
セキュリティ order_id がログイン中ユーザーのものか必ず検証

作成・修正ファイル一覧

# ファイル 種別 内容
1 mypage/invoice.php 新規 エントリーポイント
2 data/class/pages/mypage/LC_Page_Mypage_Invoice.php 新規 メインロジック(ページクラス)
3 data/class_extends/page_extends/mypage/LC_Page_Mypage_Invoice_Ex.php 新規 拡張クラス(EC-CUBE慣例)
4 data/Smarty/templates/simple/mypage/history.tpl 修正 「請求書」ボタン追加

実装手順

1. エントリーポイントの作成

mypage/invoice.php を新規作成します。
history.php と同じ構造で、対応する拡張ページクラスを呼び出すだけのシンプルなファイルです。

<?php
/*
 * ユーザー向け御請求書 PDF出力
 */

require_once '../require.php';
require_once CLASS_EX_REALDIR . 'page_extends/mypage/LC_Page_Mypage_Invoice_Ex.php';

$objPage = new LC_Page_Mypage_Invoice_Ex();
$objPage->init();
$objPage->process();

2. メインロジック(ページクラス)の作成

data/class/pages/mypage/LC_Page_Mypage_Invoice.php を新規作成します。

ポイントは3つです:

  1. 親クラス LC_Page_AbstractMypage_Ex を継承することで、未ログイン時のリダイレクトが自動で行われる
  2. $objPurchase->getOrder($order_id, $customer_id) に customer_id を渡すことで、他ユーザーの注文IDを指定した場合はエラーになる(セキュリティ)
  3. SC_Response_Ex::actionExit() を呼ぶことで、PDFを出力した後にSmartyレンダリングが走らないようにする
<?php
require_once CLASS_EX_REALDIR . 'page_extends/mypage/LC_Page_AbstractMypage_Ex.php';

class LC_Page_Mypage_Invoice extends LC_Page_AbstractMypage_Ex
{
    public function init()
    {
        parent::init();
        $this->tpl_mypageno = 'index';
        $this->tpl_subtitle = '御請求書';
        $this->httpCacheControl('nocache');
    }

    public function process()
    {
        parent::process();
    }

    public function action()
    {
        $objCustomer = new SC_Customer_Ex();
        $objPurchase = new SC_Helper_Purchase_Ex();

        // order_id の整数チェック
        if (!SC_Utils_Ex::sfIsInt($_GET['order_id'])) {
            SC_Utils_Ex::sfDispSiteError(CUSTOMER_ERROR);
        }

        $order_id = intval($_GET['order_id']);

        // ★ セキュリティ:customer_id を渡すことで他ユーザーの注文を弾く
        $arrOrder = $objPurchase->getOrder($order_id, $objCustomer->getValue('customer_id'));
        if (empty($arrOrder)) {
            SC_Utils_Ex::sfDispSiteError(CUSTOMER_ERROR);
        }

        // 発行日 = 注文日から自動取得
        $timestamp = strtotime($arrOrder['create_date']);
        $year      = date('Y', $timestamp);
        $month     = date('m', $timestamp);
        $day       = date('d', $timestamp);

        // PDF生成パラメーター(全て固定値)
        $arrData = array(
            'order_id'   => $order_id,
            'title'      => '御請求書',
            'download'   => 0,       // 0: ブラウザに開く
            'type'       => 0,       // 0: 納品書
            'year'       => $year,
            'month'      => $month,
            'day'        => $day,
            'msg1'       => 'このたびはお買上げいただきありがとうございます。',
            'msg2'       => '下記の内容にて納品させていただきます。',
            'msg3'       => 'ご確認くださいますよう、お願いいたします。',
            'disp_point' => 1,       // ポイント表記あり
            'etc1'       => '※※※※※※※※※※ 重要 ※※※※※※※※※※',
            'etc2'       => '銀行振込の場合、商品の発送は入金確認後となります。下記口座へお振り込みください。',
            'etc3'       => '振込先口座:PAYPAY銀行(0033) すずめ支店(002) 普通2281992 ユ)エグゼ',
        );

        // SC_Fpdf_Ex(管理者機能と同じエンジン)でPDF生成・出力
        require_once CLASS_REALDIR . 'SC_Fpdf.php';
        $objFpdf = new SC_Fpdf_Ex($arrData['download'], $arrData['title']);
        $objFpdf->setData($arrData);
        $objFpdf->createPdf();

        // ★ PDF出力後はここで処理を終了(Smarty描画をスキップ)
        SC_Response_Ex::actionExit();
    }
}

3. 拡張クラスの作成

EC-CUBE 2系の慣例として、ページクラスの _Ex バージョンを class_extends ディレクトリ以下に置きます。
mypage/invoice.php はこちらを直接 require_once します。

data/class_extends/page_extends/mypage/LC_Page_Mypage_Invoice_Ex.php を新規作成します。

<?php
require_once CLASS_REALDIR . 'pages/mypage/LC_Page_Mypage_Invoice.php';

class LC_Page_Mypage_Invoice_Ex extends LC_Page_Mypage_Invoice
{
    public function init()
    {
        parent::init();
    }

    public function process()
    {
        parent::process();
    }
}

補足:_Ex クラスはカスタマイズのフックポイントです。
コアを変更せず _Ex クラスだけ編集することでアップデート時の差分を最小化できます。


4. テンプレートに「請求書」ボタンを追加

data/Smarty/templates/simple/mypage/history.tpl の「再注文」フォームを修正し、
同じ row 内にBootstrapスタイルの「請求書」ボタンを追加します。

変更前:

<form action="order.php" method="post" class="margin-bottom-xl">
    ...
    <div class="row">
        <div class="col-md-4">
            <button class="btn btn-primary btn-block" name="submit">再注文</button>
        </div>
    </div>
</form>

変更後:

<form action="order.php" method="post" class="margin-bottom-xl">
    ...
    <div class="row">
        <div class="col-md-4">
            <button class="btn btn-primary btn-block" name="submit">再注文</button>
        </div>
        <div class="col-md-4">
            <a href="<!--{$smarty.const.ROOT_URLPATH}-->mypage/invoice.php?order_id=<!--{$tpl_arrOrderData.order_id|u}-->"
               target="_blank"
               class="btn btn-default btn-block">請求書</a>
        </div>
    </div>
</form>
  • target="_blank" でPDFを新しいタブで開きます
  • btn btn-default btn-block で「再注文」と同サイズ・横並びになります
  • order_id は Smarty の |u フィルタでURLエンコードしています

セキュリティについて

このような機能で必ず考慮すべき問題が「他ユーザーの請求書を閲覧できてしまわないか」です。

EC-CUBE の SC_Helper_Purchase::getOrder() は第2引数に customer_id を渡すと
そのユーザーの注文のみをSQLで絞り込んで返します
他人の order_id を指定した場合は空配列が返り、CUSTOMER_ERROR として処理されます。

// 他ユーザーの注文 → $arrOrder が空 → エラー画面へ
$arrOrder = $objPurchase->getOrder($order_id, $objCustomer->getValue('customer_id'));
if (empty($arrOrder)) {
    SC_Utils_Ex::sfDispSiteError(CUSTOMER_ERROR);
}

また、LC_Page_AbstractMypage_Ex を継承しているため、
未ログイン状態では自動的にログインページへリダイレクトされます。


テンプレートキャッシュのクリア

テンプレートを修正した後は、Smartyのコンパイル済みキャッシュを削除してください。

# Dockerを使っている場合
docker exec <コンテナ名> bash -c "find /var/www/html/data/cache -type f -delete"
docker exec <コンテナ名> bash -c "find /var/www/html/data/Smarty/cache -type f -delete"

通常環境の場合は data/cache/data/Smarty/templates_c/ 以下のファイルを削除してください。


注意点:テンプレートの種類

EC-CUBE 2系は複数のテンプレートを同時に保有できます。
現在使用しているテンプレートを確認し、対応するファイルを修正してください。

テンプレートディレクトリ 用途
templates/default/ 標準テンプレート
templates/simple/ Bootstrap3ベースのレスポンシブテンプレート
templates/sphone/ スマートフォン専用テンプレート
templates/mobile/ ガラケー用テンプレート(現在ほぼ不要)

現在使用しているテンプレートは管理者画面の
システム設定 → パラメーター設定 → CONST_TEMPLATE_NAME で確認できます。


まとめ

項目 内容
追加ファイル 3ファイル(invoice.php, ページクラス2つ)
修正ファイル 1ファイル(history.tpl)
流用した既存機能 SC_Fpdf_Ex(管理者向け帳票出力エンジン)
セキュリティ対策 ログインチェック(AbstractMypage)+所有者確認(getOrder with customer_id)

管理者向けに実装済みの PDF エンジンをそのまま活用できるため、
追加コードは最小限に抑えられています。

By h.Bogy