
functions.phpが自己修復するマルウェアに感染 WordPressの持続的再感染を完全に駆除する方法
functions.php に SC_TH_BEGIN 〜 SC_TH_END で囲まれた難読化コードが注入され、完全削除したはずなのに数週間後に自己修復して再出現する感染は、単一ファイルの削除では解決しない。この種のマルウェアは、複数の隠れた感染拠点から自己を再生し、クリーンアップの試みを検知して適応する高度な仕組みを持つ。根本的な駆除には、侵入経路の遮断と全ファイルの網羅的スキャンが欠かせない。
SC_TH_BEGIN型マルウェアの感染メカニズムと自己修復の仕組み

この手の持続的感染は、単純なワンライナーではなく、組織化されたキャンペーンの一環として設計されている。コードは functions.php の末尾に注入され、SC_TH_BEGIN と SC_TH_END という独自タグで囲まれる。内部にはバージョン番号とハッシュ値が含まれ、これが改ざん検知と自己修復のトリガーになる。
感染のライフサイクルは次の3段階で進行する。まず初期侵入時に、難読化された本体コードが mu-plugins ディレクトリに隠しファイルを書き込む。このファイルがバックドアとして機能し、定期的に functions.php の状態を監視する。次に、functions.php から感染コードが削除されると、mu-plugins の隠しファイルがハッシュ不一致を検知し、自身のロジックで functions.php を再感染させる。さらに、アップロードディレクトリには一見無害なアーカイブファイルが置かれ、これが外部からの指令受け取り口や、別の復旧ポイントとして機能する。
/* SC_TH_BEGIN v2.1 a3f8c... */
$abc = base64_decode('UEhO...');
eval($abc);
/* SC_TH_END v2.1 a3f8c... */// functions.php のクリーンな終了
/* SC_TH_BEGIN v3.0 b7d2e... */ // this file previously had malicious content but it's been removed and is safe now /* SC_TH_END v3.0 b7d2e... */
3つの感染拠点のうち、最も見落とされやすいのは mu-plugins の隠しファイルだ。このディレクトリはプラグイン管理画面に表示されないため、手動での確認が必須になる。また、コメント行だけが残る偽装パターンは、マルウェアがクリーナーの動作を学習し「おとり」として置いている可能性が高い。
自己修復するマルウェアを根本から除去する駆除手順

単に functions.php から感染コードを削除するだけでは、裏で動く監視機構が再び書き込んでしまう。以下の手順では、感染のサイクルを断ち切るために、すべての拠点を同時に無効化する。
全ファイルのバックアップと感染範囲の特定
サーバー全体のファイルをローカルにダウンロードし、安全な環境でスキャンする。この段階ではまだサーバー上のファイルには手を加えず、何がどこに潜んでいるかを把握することが目的だ。隠しファイルは先頭にドット(.)が付くものや、ランダムな文字列のファイル名になっていることが多い。
mu-plugins ディレクトリの全ファイルを精査する
wp-content/mu-plugins/ に存在するファイルのうち、自身で設置した覚えのないものはすべて疑う。特に、ファイル名が意味不明な文字列だったり、PHP ファイルでありながらプラグインヘッダーがないものはマルウェアの可能性が高い。正常な mu-plugin も一時的に退避させ、ディレクトリを空にしてから必要なものだけ戻す方法が確実だ。
アップロードディレクトリ内の不審なアーカイブとPHPファイルを削除する
wp-content/uploads/ 以下に .zip や .tar.gz などのアーカイブファイルが存在した場合、それが正規のバックアップやプラグイン由来でない限り削除する。PHPファイルも画像などに偽装されて存在することがあるため、拡張子に関係なくファイルの先頭数行を確認し、PHPタグが含まれていないか検査する。
テーマとプラグインを公式ソースと比較して復元する
改ざんの可能性があるテーマやプラグインは、公式リポジトリからダウンロードしたクリーンなファイルで上書きする。子テーマの functions.php だけが標的になっていたとしても、親テーマや他のプラグインに仕込まれたバックドアが感染を再開させることがあるため、疑わしい拡張機能はすべて置き換える。
クリーンアップ後に再感染を防ぐための恒久対策

ファイル改ざん監視を導入する
WordPress のコアファイルやテーマ、プラグインの変更をリアルタイムで検知するセキュリティプラグインを導入する。改ざんが発生した瞬間に通知を受け取れるため、感染の早期発見につながる。ファイル整合性チェック機能を持つものを選び、既知のクリーンな状態との差分を定期的に比較する設定にしておく。
書き込み権限の厳格化
functions.php や mu-plugins ディレクトリに対して、Web サーバーの実行ユーザーが書き込みできないようにパーミッションを設定する。通常、PHP ファイルは 644、ディレクトリは 755 が基本だが、特に標的になりやすいファイルは 444 に設定して変更を防止する。ただし、テーマやプラグインの自動更新を利用している場合は、更新時に権限を一時的に戻す運用が必要になる。
使用していないプラグインとテーマの完全削除
無効化されているだけのプラグインやテーマも、ファイル自体がサーバー上に残っていれば攻撃の入り口になる。WordPress の管理画面から完全に削除し、ディレクトリごと消去する。休眠中の拡張機能は更新が止まっていることが多く、既知の脆弱性を放置することになる。
よくある質問
functions.php の感染コードを手動で削除するだけではなぜダメなのか
感染コード自体が別の場所にバックドアを設置しており、そのバックドアが functions.php の状態を監視しているためだ。削除を検知すると自動的に再書き込みが行われ、さらにバージョン番号を上げて「対策済み」を装うケースもある。感染コードの削除と同時に、すべてのバックドアを無効化しなければ根本的な解決にはならない。
mu-plugins ディレクトリに心当たりのないファイルがあるが、削除しても問題ないか
mu-plugins は「マストユースプラグイン」と呼ばれ、有効化操作なしで自動的に読み込まれる特殊なディレクトリだ。正規のファイルはプラグイン名や機能がわかる名前になっていることが多い。ランダムな文字列や .php 以外の拡張子を持つファイルはマルウェアの可能性が高い。まずすべてを退避させ、サイトが正常に動作することを確認してから、必要なものだけ戻す手順が安全だ。
アップロードディレクトリ内の .zip ファイルはすべて削除すべきか
自身でアップロードした覚えのないアーカイブファイルは削除する。正規のプラグインやテーマが生成するバックアップファイルもあるが、マルウェアがアーカイブを設置する場合、ファイル名が日付とは無関係な文字列だったり、設置日時が不自然に新しいことが多い。不安な場合は、ファイルをダウンロードして中身を確認し、PHPコードや難読化されたスクリプトが含まれていないか検査する。
感染を完全に駆除したかどうかをどう確認すればいいか
セキュリティスキャナーを複数かけ、感染の痕跡が検出されないことを確認する。さらに、functions.php のハッシュ値を記録し、1週間後、2週間後と定期的に比較して変化がないことを検証する。サーバーのアクセスログも確認し、不審な POST リクエストや、管理画面外からの PHP ファイルへの直接アクセスがないかを監視する。
SC_TH_BEGIN タグは特定のマルウェアファミリーの特徴か
このタグは、標的のサイトを識別し、感染状況を管理するためのキャンペーン固有のマーカーと考えられる。バージョン管理とハッシュ検証の仕組みから、手動での駆除を想定した設計になっている点が特徴的だ。未知のマルウェアファミリーである可能性もあり、一般的なマルウェアスキャナーの定義ファイルが追いついていない場合がある。
この記事のポイント
- functions.php だけの削除では自己修復型マルウェアの再感染を防げない
- mu-plugins の隠しファイルと uploads 内のアーカイブが感染の復旧ポイントになる
- 全テーマ・プラグインを公式ソースで上書きし、バックドアを一掃する
- パーミッションの厳格化とファイル改ざん監視で恒久的な防御を敷く
- 駆除後はハッシュ値の定期比較で再感染の兆候を早期発見する

・ Reddit、Stack Overflow、WordPress.org フォーラムを日々巡回し、現場の悩みを拾い上げて記事化
・ WordPress、WooCommerce、Next.js などモダンWeb制作領域のトラブルシューティングが専門
・ 「検索しても答えが見つからなかった」を一つでも減らすことが目標
・ エラーメッセージから根本原因にたどり着く粘り強い調査が得意
・ 初心者がつまずきやすい箇所を先回りで解決する記事作りを心がけている

WooCommerce 決済手数料が大文字IDで効かない時の直し方
WooCommerce で「Checkout Fees for WooCommerce」などのプラグインを使い、支払い方法ごとに手数料を設定しているのに、特定の決済ゲートウェイだけルールがまったく反応しない。大文字を含む ID を持つ決済方法でこの症状が出ているなら、プラグイン内部の ID 比較処理で大文字と小文字が一致せずに無視されている可能性が高い。functions.php に1行のフィルターフックを追加するだけで即座に解消する。
なぜ特定の決済方法だけ手数料や割引が適用されないのか

Checkout Fees for WooCommerce は、支払い方法の ID をキーにして「どのゲートウェイに手数料をのせるか」を管理している。設定画面でルールを保存するとき、プラグインは WordPress の sanitize_key() 関数を通して ID をすべて小文字に変換し、データベースに格納する。ところが実際のチェックアウト画面で選択された決済方法の ID を取得する段階では、この小文字化(正規化)が行われない。
その結果、もともと ID が小文字のみで構成されているゲートウェイ(例 wc_zibal)は、保存値と取得値が同じ文字列になるため問題なく動く。一方で WC_Sep_Payment_Gateway や WC_Gateway_TorobPay のように大文字を含む ID の場合、保存された wc_sep_payment_gateway というキーと、実際にチェックアウト時に渡される WC_Sep_Payment_Gateway という文字列が一致せず、該当するルールが「存在しない」と判定されてしまう。
手数料設定が反応しない原因を視覚的に理解する

wc_sep_payment_gatewayWC_Sep_Payment_Gatewaywc_sep_payment_gatewaywc_sep_payment_gatewayfunctions.php にフィルターフックを追加して即座に解決する手順

プラグイン本体のコードを直接編集しなくても、子テーマの functions.php に数行のコードを追加するだけでこの問題は修正できる。以下のフィルターフックは、チェックアウト時に渡されるゲートウェイ ID を sanitize_key() で小文字に揃え、プラグインが正しくルールを照合できるようにする。
追加するコード
add_filter( 'alg_wc_add_default_gateway_on_cart', function ( $gateway ) {
return sanitize_key( $gateway );
} );このフィルターフックは、プラグインが決済ゲートウェイの ID を参照する直前に割り込み、ID を小文字だけの文字列に変換する。もともと小文字のゲートウェイには何の影響も与えず、大文字を含む ID だけが正規化される。コードを追加したあとは、WooCommerce のシステムステータス画面から Transients(一時データ)を削除するか、WP Rocket や W3 Total Cache などのキャッシュプラグインを使っているなら全キャッシュのクリアを忘れない。
どうしても functions.php を触りたくない場合の代替手段
コードの追加に抵抗がある場合、Code Snippets プラグインをインストールして同じコードをスニペットとして登録する方法も有効だ。管理画面の「スニペット」→「新規追加」から上記のコードを貼り付け、「サイトのフロントエンドで実行」を選択して有効化すれば、テーマファイルを直接編集せずに済む。
決済ゲートウェイごとの手数料が今後も安定して動くようにするために

このバグは Checkout Fees for WooCommerce の無料版に限らず、同様の仕組み(sanitize_key で保存し、未正規化の ID と比較する)を持つ他の手数料系プラグインでも発生しうる。ID に大文字を使う決済ゲートウェイは海外のプロバイダーに多く見られ、特に中東やアジア圏のローカル決済サービスを WooCommerce に追加している場合に遭遇しやすい。
根本的にはプラグイン開発元が比較処理の前段で正規化を実装することが望ましいが、今回紹介したフィルターフックを適用しておけば、プラグインのアップデート後も変更が上書きされる心配はほぼない(functions.php または Code Snippets に追加したコードはプラグイン更新の影響を受けない)。
また、新しく決済ゲートウェイを追加したときにも同じ問題が起きるか事前にチェックする習慣をつけると、売上に直結するチェックアウト画面のトラブルを未然に防げる。テスト用の注文を入れて手数料が正しく加算されるか、割引が適用されるかを確認しておく。
よくある質問
この修正は Checkout Fees for WooCommerce の有料版でも必要か
本記事執筆時点では、無料版と有料版でゲートウェイ ID の正規化ロジックに違いは確認されていない。有料版でも同様に大文字混在の ID でルールが動作しなくなる場合があるため、症状が出たら同じフィルターフックを試して問題ない。
すべて小文字の ID しか使っていないが、念のためコードを追加しても害はないか
sanitize_key() はすでに小文字の文字列には何も変更を加えない。もともと正常に動いている環境にコードを追加しても、挙動が変わることは一切なく、安全に設置できる。
functions.php を編集したら画面が真っ白になった
PHP の構文エラーが原因で「このサイトで重大なエラーが発生しました」と表示されることがある。コードの貼り付け位置やセミコロンの抜けを確認する。FTP やホスティングのファイルマネージャーで functions.php を開き、追加したコードをいったん削除して復旧させてから、Code Snippets プラグイン経由で再度追加するほうが安全だ。
カスタマイズしたコードがプラグインのアップデートで消えたりしないか
子テーマの functions.php に書いたコードや Code Snippets プラグインに登録したスニペットは、プラグイン本体のアップデートとは完全に独立して保存される。アップデートのたびに再設定する必要はない。
別の手数料プラグインでも同じ現象が起きるか
ゲートウェイ ID を sanitize_key() で保存し、比較時に正規化していないプラグインであれば、同様の不具合が起こる。WooCommerce の決済ゲートウェイ関連プラグインは広くこの設計パターンをとっており、大文字を含む ID を持つ決済手段を導入したとたん手数料が効かなくなる、という報告は定期的に見られる。
この記事のポイント
- 大文字を含む決済ゲートウェイ ID で手数料が効かないのは、保存時と取得時の文字列の不一致が原因
- functions.php に1行のフィルターフックを追加すれば、即座に大文字・小文字の差を吸収できる
- コード編集が不安なら Code Snippets プラグインを使うと同じ修正を安全に適用できる
- プラグインのアップデートや新しい決済手段の追加時に備えて、テスト注文での動作確認を習慣化する

・ Reddit、Stack Overflow、WordPress.org フォーラムを日々巡回し、現場の悩みを拾い上げて記事化
・ WordPress、WooCommerce、Next.js などモダンWeb制作領域のトラブルシューティングが専門
・ 「検索しても答えが見つからなかった」を一つでも減らすことが目標
・ エラーメッセージから根本原因にたどり着く粘り強い調査が得意
・ 初心者がつまずきやすい箇所を先回りで解決する記事作りを心がけている

カートが0円だとWooCommerceの注文が完了しない時の直し方
なぜカート合計が0円だとチェックアウトが完了しないのか

WooCommerce の最低購入金額制御プラグイン(たとえば Minimum Purchase Amount Control など)は、指定した金額未満の注文を受け付けないように設計されている。このプラグインは通常、カートの合計金額が設定した最低金額を下回るとチェックアウトをブロックする。しかし「無料商品を許可する」「クーポンで0円になる場合を許可する」といった例外ルールを設定していても、内部的にはチェックアウト処理の最終段階で金額を再評価し、ブロックをかけてしまうケースがある。
このとき画面上では「注文する」ボタンを押せてしまい、一見すると購入が完了したように見える。ところが実際には注文データが正しく確定されず、商品がカートに取り残される。リダイレクト先の注文確認ページ(サンキューページ)にも遷移しない。プラグインを停止すると問題が消えることから、プラグインのゼロ円ブロック機能が直接の原因だ。
フィルターフックでゼロ円チェックアウトを許可する手順

このプラグインには、ゼロ円の注文を許可するための専用フィルターフック ct_mpac_allow_zero_total が用意されている。このフックを有効にすれば、無料商品やクーポンで合計が0円になったカートでも問題なくチェックアウトが完了するようになる。
以下の手順では、子テーマの functions.php に数行のコードを追加する。子テーマを使っていない場合は、Code Snippets プラグインなどを利用しても同じ結果が得られる。
functions.php を開く(外観 → テーマファイルエディター)追加するコードは次のとおりだ。
add_filter( 'ct_mpac_allow_zero_total', '__return_true' );この1行で、プラグインがゼロ円の注文を許可するようになる。__return_true は WordPress の組み込み関数で、単純に true を返す。フィルターにこれを渡すことで「常に許可する」という挙動に切り替わる仕組みだ。
Code Snippets プラグインを使う場合は、新規スニペットを追加し、上記のコードを貼り付けて「サイト全体で実行」に設定すればよい。
コード追加後も改善しない場合の確認ポイント

フィルター名がプラグインのバージョンと合っているか確認する
プラグインのバージョンアップによってフィルター名が変更されている可能性がある。プラグインの公式ドキュメントやソースコード内で apply_filters を検索し、最新のフック名を確認するのが確実だ。特にプラグイン名が似ている別の最低購入金額プラグインに乗り換えた場合は、フック名がまったく異なるため注意が必要になる。
キャッシュ系プラグインやサーバーキャッシュの影響を疑う
functions.php を更新したのに改善しない場合、WordPress のキャッシュプラグインやサーバーレベルのキャッシュ(OPcache など)が原因で変更が反映されていないことがある。キャッシュをすべてクリアし、さらにブラウザのシークレットモードで動作をテストしてみる。OPcache が効いているレンタルサーバーでは、ファイル更新から数分間は古いコードが動き続けるケースもある。
決済ゲートウェイ側のゼロ円制限を調べる
ごくまれに、WooCommerce の決済ゲートウェイ自体が0円のトランザクションを許可していない場合がある。プラグインの問題が解消されたあともチェックアウトが進まないときは、Stripe や PayPal などのゲートウェイ設定で「0円の注文を許可する」オプションが存在しないか確認する。テスト用に代金引換や銀行振込など、実決済を伴わないゲートウェイに切り替えて試すのも有効な切り分け方法だ。
よくある質問
このフィルターはどのプラグインに対応しているのか
ct_mpac_allow_zero_total は、主に「Minimum Purchase Amount Control for WooCommerce」系のプラグインで使われるフックだ。プラグインの作者が異なるとフック名も変わるため、必ず利用中のプラグインのドキュメントを参照してほしい。
フィルターを追加する以外の解決策はあるか
プラグインの設定画面で「無料商品を許可」「クーポン適用後の0円を許可」といったチェックボックスが用意されていれば、そちらを有効にするだけで直ることもある。設定が存在しない場合や、設定を入れても効かないときにフィルターを使うのが確実な方法だ。
ゼロ円の注文を許可するとセキュリティ上のリスクはあるか
意図した無料商品や割引クーポン以外で0円になるカートが発生しないよう、クーポンの利用条件や商品価格の設定を適切に管理していれば大きなリスクにはならない。ただし、テストや不正利用対策として、定期的に0円注文のログを確認する運用は推奨する。
子テーマを使わずにコードを追加する方法はあるか
Code Snippets プラグインや WPCode プラグインを利用すれば、テーマファイルを直接編集せずにフィルターフックを安全に追加できる。テーマのアップデートでコードが消える心配もなく、管理画面からオンオフを切り替えられるため、まずはこちらを試すのが安心だ。
この記事のポイント
- WooCommerce の最低購入金額プラグインが0円の注文をブロックし、チェックアウトが完了しない
- フィルターフック
ct_mpac_allow_zero_totalをfunctions.phpに追加すればゼロ円注文を許可できる - Code Snippets プラグインを使えば子テーマの編集なしで安全にコードを追加可能
- 改善しない場合はキャッシュのクリアや決済ゲートウェイの0円制限も確認する

・ Reddit、Stack Overflow、WordPress.org フォーラムを日々巡回し、現場の悩みを拾い上げて記事化
・ WordPress、WooCommerce、Next.js などモダンWeb制作領域のトラブルシューティングが専門
・ 「検索しても答えが見つからなかった」を一つでも減らすことが目標
・ エラーメッセージから根本原因にたどり着く粘り強い調査が得意
・ 初心者がつまずきやすい箇所を先回りで解決する記事作りを心がけている

WooCommerce処理中注文を未払いのまま請求書プラグインに連携する方法
WooCommerceで銀行振込(BACS)や後払い決済を使う場合、注文を「処理中」にした途端に、外部の請求書発行プラグインがその注文を「支払い済み」と認識してしまうことがある。この原因は、WooCommerceが処理中ステータスに遷移する際に支払い日(_date_paid)を自動で記録し、多くのプラグインがそのメタデータを支払い完了の合図として読み取るからだ。ここではこの仕組みを詳しく解説し、後払い注文を未払いのまま連携させる確実な修正方法を紹介する。
なぜ処理中になった注文が「支払い済み」扱いになるのか

WooCommerceの内部動作として、注文が「処理中(processing)」または「完了(completed)」に切り替わると、maybe_set_date_paid()というメソッドが呼ばれ、現在の日時を_postmetaテーブルの_date_paidに保存する。この動作は、クレジットカード決済など即時払いが完了した瞬間を記録するためにある。ところが銀行振込(BACS)は標準で「保留中(on-hold)」になるが、運用上「処理中」に手動変更したり、コードで処理中に固定すると、実際には入金前でも支払い日が書き込まれてしまう。
請求書発行プラグイン(WooCommerce PDF Invoicesや会計連携プラグインなど)は、通常この_date_paidをもとに「支払い済み」かどうかを判断する。さらに、WC_Orderクラスのis_paid()メソッドは内部的に「処理中」「完了」といったステータスを見てtrueを返す設計のため、_date_paidが空でも「支払い済み」と扱われるケースも多い。結果として、入金前の後払い注文が会計ソフト上で「支払い済み請求書」として取り込まれてしまうのだ。
上図のように、_date_paidの書き込みを止めるか、支払い済み判定のロジック自体を書き換えることで、請求書が正しく「未払い」で連携されるようになる。次に、具体的にどう対処すればよいかを方法別に紹介する。
支払い済み判定のメカニズムを特定する

修正に入る前に、自分が使っている請求書発行プラグインがどのタイミングで「支払い済み」と判断しているかを知っておくと無駄な試行錯誤が減る。大きく分けると以下の3パターンがあり、フィルターの効き方が変わる。
- _date_paidのメタデータを直接 get_post_meta() や$order->get_meta(‘_date_paid’) で読み取っている
- WC_Order::get_date_paid() メソッドを呼び出している(フィルターフック posible)
- WC_Order::is_paid() メソッドで判定している(ステータスベース)
多くのプラグインは最後のis_paid()や、直接メタデータを読む形をとる。get_date_paid()フィルターだけでは直らなかった場合、is_paid()の返り値を書き換えるか、そもそも_date_paid自体を保存させないアプローチが有効だ。
強制的に未払いにする3つの方法

方法1. _date_paidが記録されるのを根本から防ぐ
WooCommerceが処理中ステータスに変わったときに_date_paidをセットするのは、maybe_set_date_paid()の中で「このステータスは支払い完了とみなす」という配列に「processing」が含まれているからだ。この配列はwoocommerce_payment_complete_order_statusフィルターで変更できる。以下のコードをテーマのfunctions.phpまたはCode Snippetsプラグインで追加すれば、BACS(bank transfer)決済の注文でのみ「processing」を支払い完了ステータスから外せる。
add_filter('woocommerce_payment_complete_order_status', function($statuses, $order) {
if ($order instanceof WC_Order && 'bacs' === $order->get_payment_method()) {
$statuses = array_filter($statuses, function($s) {
return $s !== 'processing';
});
}
return $statuses;
}, 10, 2);これにより、BACSの注文が「処理中」に移行しても、WooCommerceは「これは支払い完了ステータスではない」と認識し、_date_paidを一切書き込まない。注文メモなどに残る変わったログも出ず、動作は非常にクリーンだ。結果として、請求書プラグインが_date_paidを見ている限り、未払いのままとなる。
方法2. is_paid()の返り値を上書きする
もし日付メタデータを消してもなお請求書が「支払い済み」になってしまう場合は、プラグインがWC_Order::is_paid()メソッドを使っている可能性が高い。このメソッドは内部的にwc_get_is_paid_statuses()が返すステータス配列(デフォルトでは’processing’と’completed’)と照合し、trueを返す。こちらもフィルターでBACSだけ処理中を除外できる。
add_filter('woocommerce_order_is_paid_statuses', function($statuses, $order) {
if ($order instanceof WC_Order && 'bacs' === $order->get_payment_method()) {
$statuses = array_diff($statuses, array('processing'));
}
return $statuses;
}, 10, 2);このコードを追加すると、is_paid()は見かけ上「処理中」でもfalseを返すため、請求書プラグインは「未払い」と判断する。方法1と併用すれば、_date_paidの直接読み取りもis_paid()経由の判定も両方ブロックできる。
方法3. プラグイン側のエクスポートフィルターを利用する
どうしても上記のフィルターで直らない、あるいは他プラグインとの兼ね合いで処理中ステータスを支払い完了扱いのままにしたい場合は、請求書発行プラグインが用意しているデータ書き換え用のフックを使う。たとえば、WooCommerce PDF Invoices & Packing Slips であれば wpo_wcpdf_document_data や wpo_wcpdf_invoice_data といったフィルターがある。請求書に含める「支払い済み」フラグや日付を、注文の支払い方法がBACSのときだけ空にすることで解決できる。
プラグインのフィルター一覧は開発元のドキュメントで確認する必要があるが、一般に「Paid = 1」や「paid_date」といったキーを書き換えることが多い。次のサンプルは、WooCommerce PDF Invoicesで支払い状況を未払いに戻す例だ。
add_filter('wpo_wcpdf_invoice_data', function($data, $document) {
if ($document->order instanceof WC_Order && 'bacs' === $document->order->get_payment_method()) {
$data['paid'] = 0;
$data['date_paid'] = '';
}
return $data;
}, 10, 2);なお、プラグインが読み取るフィールド名は製品によって異なるため、実際のソースコードや開発元への問い合わせで正確なキー名を調べるのが確実だ。
カスタム注文ステータスで処理中と区別する方法

根本的に「後払い専用の処理中ステータス」をWooCommerceに追加するという手もある。標準の処理中ステータスは、支払い完了後の発送準備として使われる場面が多い。これに対し、後払いは「入金確認前だが、すでに注文を受け付け、請求書を発行したい」という微妙な状態だ。そこで「後払い処理中」のようなカスタムステータスを作れば、WooCommerceの支払いロジックに干渉せず、かつ請求書プラグインも処理中ではないため誤って支払い済み扱いしなくなる。
function register_custom_order_status_invoice_processing() {
register_post_status('wc-invoice-processing', array(
'label' => '後払い処理中',
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'exclude_from_search' => false,
'label_count' => _n_noop('後払い処理中 (%s)', '後払い処理中 (%s)')
));
}
add_action('init', 'register_custom_order_status_invoice_processing');
add_filter('wc_order_statuses', function($order_statuses) {
$order_statuses['wc-invoice-processing'] = '後払い処理中';
return $order_statuses;
});
このステータスをBACSの注文に割り当てれば、標準の処理中ルートを通らずに済む。ただし、配送プラグインや在庫連動がこのカスタムステータスを「処理中」同様に扱うよう追加のフックが必要になるケースもある。その場合は、woocommerce_order_is_paid_statusesフィルターなどで「wc-invoice-processing」も支払い完了とみなしたい処理にだけ含めると調整できる。
よくある質問
この修正を適用すると、クレジットカードの処理中注文まで未払いになりませんか
紹介したコードはいずれも if 文で’ bacs ‘決済に限定しているため、クレジットカードや他の即時決済には影響しない。条件を厳密に書けば、安全に導入できる。
後払い注文のステータスをカスタムステータスにしたら、WooCommerceの標準メールは飛びますか
新規ステータスを追加しただけでは自動的にメールは送信されない。woocommerce_order_status_invoice-processing のようなアクションフックを利用して、独自のメールテンプレートをトリガーするか、処理中と同じメールを送るように設定する必要がある。
どの方法を選ぶべきかの判断基準は
まずは方法1と方法2を組み合わせて適用してみるのが最も手軽で汎用性が高い。それで解決しない場合はプラグイン固有のフィルターを探す。長期的に後払いワークフローを整理したいならカスタムステータスがおすすめだ。
この記事のポイント
- 処理中へのステータス変更で_date_paidが自動保存される仕様が原因
- woocommerce_payment_complete_order_statusフィルターで_date_paid書き込みをBACSだけ無効化できる
- is_paid()の返り値を上書きすれば、日付以外の「支払い済み」判定もブロック可能
- 請求書プラグイン固有のフィルターがあれば、そこからも支払い情報を空にできる
- 後払い専用のカスタム注文ステータスを導入すれば、処理中と混同せずに管理できる

・ Reddit、Stack Overflow、WordPress.org フォーラムを日々巡回し、現場の悩みを拾い上げて記事化
・ WordPress、WooCommerce、Next.js などモダンWeb制作領域のトラブルシューティングが専門
・ 「検索しても答えが見つからなかった」を一つでも減らすことが目標
・ エラーメッセージから根本原因にたどり着く粘り強い調査が得意
・ 初心者がつまずきやすい箇所を先回りで解決する記事作りを心がけている
