EC-CUBE4 PHP ShoppingController Twig セッション

EC-CUBE 4では、注文確認画面を開いたまま別のタブで別のカートの購入手続きを進めると、「注文する」ボタンで確定する受注が、確認画面に表示していたものとは別のカートの受注に入れ替わります。防ぐには、確認画面のフォームに受注IDを持たせ、注文処理の入口で照合します。この挙動は4.0.0から4.3まで本体に残っているので、複数の販売種別を使うサイトは自分で塞ぐ必要があります。

確認画面の受注IDを先頭カートの受注IDと照合し、一致なら注文処理へ進み、不一致なら注文処理に入らず購入エラー画面へ移る流れ

確認画面の受注IDをフォームに持たせ、注文処理で照合する

修正は2か所です。注文確認画面のテンプレート Shopping/confirm.twig で、CSRFトークンの直後に受注IDのhidden項目を追加します。

<form id="shopping-form" method="post" action="{{ url('shopping_checkout') }}">
    {{ form_widget(form._token) }}
    {# 確認画面で表示している受注のIDを注文処理へ送る #}
    <input type="hidden" name="order_id" value="{{ Order.id }}">

次に ShoppingController::checkout() の「受注の存在チェック」の直後で、送られてきた受注IDと、これから確定する受注のIDを比べます。違っていれば注文処理に入らず購入エラー画面へ移動します。

// 受注の存在チェック(本体のコードそのまま)
$preOrderId = $this->cartService->getPreOrderId();
$Order = $this->orderHelper->getPurchaseProcessingOrder($preOrderId);
if (!$Order) {
    return $this->redirectToRoute('shopping_error');
}

// 確認画面で表示した受注と、先頭カートに紐づく受注が異なる場合は中断する
// (別のタブで別のカートの購入手続きを進めた場合に起きる)
if ((string) $request->request->get('order_id') !== (string) $Order->getId()) {
    log_info('[注文処理] 確認画面の受注と購入処理中の受注が一致しません.', [
        $request->request->get('order_id'),
        $Order->getId(),
    ]);
    $this->addError('ご注文内容が変更されています。お手数ですが、カートから再度お手続きください。');

    return $this->redirectToRoute('shopping_error');
}

注文処理に入る前に止めるので、カートの削除や決済の呼び出しには影響しません。

確定する受注は確認画面ではなく「先頭カート」で決まる

EC-CUBE 4は販売種別ごとにカートを分けて持ち、どのカートを購入手続きに進めるかを「先頭カート」という並び順で管理しています。カート画面の「レジに進む」(cart_buystep)が CartService::setPrimary() で押したカートを先頭に入れ替え、注文手続き画面も「注文する」(shopping_checkout)も、先頭カートの pre_order_id から受注を取り出します。確認画面のフォームには受注を特定する値が入っていません。

「注文する」を押した瞬間に先頭カートが別物なら、そちらの受注が確定します。再現は3手順です。タブAでカートAの確認画面まで進み、タブBでカートBの「レジに進む」を押して注文手続き画面を開き、タブAに戻って「注文する」を押す。確定するのはカートBの受注で、支払方法は新規作成時の既定値(利用可能なもののうち sort_no が最大のもの)のまま、カード情報などの入力も通っていません。決済代行を呼ぶ支払方法なら入力不足のエラーで止まりますが、決済を伴わない支払方法が既定値になっていると、確認画面を一度も通っていない受注がそのまま確定します。

決済代行の戻りURLでも先頭カートに頼らない

同じ問題は、3Dセキュア認証などで外部サイトへ移動してから戻ってくる処理にもあります。戻り先のコントローラが先頭カートから受注を探す作りの場合、カード会社の画面にいる間に別のタブで操作されると、認証した受注が見つからないか、別の受注を見てしまいます。決済プラグインの戻り先コントローラで getPreOrderId() を検索すると、この作りかどうかが分かります。

対処の考え方は2つです。

  • 外部サイトへ移動する前に受注IDをセッションへ保存し、戻り先ではその受注IDで取り出す。受注IDをURLに含める方式なら、加えて受注が「決済処理中」であることと、ログイン中の会員のものであることを確認する。
  • 確定後に削除するカートは先頭カートではなく、受注の pre_order_id と同じ値を持つカートを選ぶ。一致するカートが無ければ削除しない。

受注の内容は確認画面を通った時点で確定していて、戻ってきた時点のカートの中身には影響されません。戻り先でカートが必要なのは「受注を見つける」ことと「カートを消す」ことだけなので、この2つを受注側の情報で行えば、別のタブで何をされていても正しい受注が確定します。

日本発!ECオープンプラットフォーム「EC-CUBE」 EC-CUBEゴールドパートナー EC-CUBEは株式会社イーシーキューブの商標です

EC-CUBEに関するお問い合わせ


    [重要]現在公式にセキュリティサポートが切れていないPHPは8.1以上、MySQLは8.0以上で、対応しているEC-CUBEバージョンは4.2以上です。古いEC-CUBEを使っている方は適切なタイミングでバージョンアップをご検討ください。

    EC-CUBEバージョンアップ