WooCommerce 購物車與結帳區塊的架構與資料流

購物車與結帳這兩個區塊是整個 WooCommerce 區塊化程度最高、也最容易讓有經驗的開發者踩坑的地方,原因是它們表面上還是「一個頁面」,實際上運作方式跟舊版完全不同 — 舊版是 PHP 產生表單、送出後整頁重載;新版是 React 畫的介面,透過 Store API 跟後端溝通。

先看舊版怎麼運作

回憶一下舊版結帳頁的生命週期:

  1. 使用者打開 /checkout/,PHP 執行短代碼 woocommerce_checkout
  2. WooCommerce 跑一連串 hook 組出 HTML 表單(woocommerce_checkout_fields 這時候生效)
  3. 使用者填完按「下單」,表單 POST 到伺服器
  4. PHP 驗證、建立訂單、呼叫金流的 process_payment()
  5. 回傳導向網址,整頁跳轉

整個流程的重心在伺服器端,前端只是一張表單。所以要改任何東西,改 PHP 就對了。

新版的生命週期

區塊結帳頁完全不同:

  1. 使用者打開 /checkout/,PHP 只輸出一個容器加上初始資料
  2. React 接手,打 GET /wc/store/v1/cart 拿購物車狀態,畫出表單
  3. 使用者每改一個東西(地址、運送方式、折價券),前端就打對應的 API,拿回完整的購物車狀態再重繪
  4. 按下單時前端打 POST /wc/store/v1/checkout,把表單資料一次送出
  5. 後端建立訂單、跑金流,回傳 payment_result
  6. 前端依 payment_result 決定要導向哪裡

差別在於:表單長什麼樣是前端決定的,後端只提供資料與 schema。 你在 PHP 端改 HTML 沒有意義,因為那段 HTML 根本不存在。

購物車端點回傳什麼

實測一下(這是公開端點,不用登入):

$ curl https://your-site.com/wp-json/wc/store/v1/cart

回傳的頂層欄位:

items, items_count, items_weight, totals, fees, coupons,
shipping_rates, shipping_address, billing_address,
needs_payment, needs_shipping, has_calculated_shipping,
payment_methods, payment_requirements, cross_sells, errors, extensions

幾個值得注意的:

  • totals 是後端算好的,前端不做任何金額計算 — 這很重要,代表折扣、稅、運費的邏輯全部留在 PHP,前端只負責顯示
  • shipping_rates 是可選的運送方式清單,選了之後打 /cart/select-shipping-rate 會拿回重算過的 totals
  • errors 是結構化的錯誤,前端據此顯示訊息
  • extensions 是留給你的空間,等一下會講

結帳端點 POST /wc/store/v1/checkout 接受的欄位則是:

billing_address, shipping_address, customer_note,
payment_method, payment_data, extensions

回傳裡最關鍵的是 payment_result:

{
  "payment_result": {
    "payment_status": "success",
    "redirect_url": "https://your-site.com/checkout/order-received/123/..."
  }
}

前端拿到 payment_status 與 redirect_url 之後才決定跳轉。這個結構在之後串金流時會非常關鍵,因為你的金流要把使用者導到哪裡,是透過這個欄位傳回前端的。

為什麼你熟悉的 hook 失效了

看這幾個常見的舊 hook:

舊 hook在區塊結帳頁
woocommerce_checkout_fields❌ 無效,表單不是 PHP 產生的
woocommerce_after_checkout_billing_form❌ 無效,沒有這個插入點
woocommerce_checkout_process⚠️ 部分情況不觸發
woocommerce_checkout_create_order✅ 有效,訂單仍由 PHP 建立
woocommerce_thankyou✅ 有效
woocommerce_order_status_*✅ 有效

規律很清楚:跟「畫面」有關的 hook 失效,跟「訂單」有關的 hook 仍然有效。 因為訂單的建立、金流的處理、狀態的變化都還在 PHP 端跑,只有介面那一層搬到前端去了。

這也是為什麼之後提到金流、發票、物流時,很多做法跟舊版是一樣的,那些都發生在後端,只有當要新增欄位時,因為牽涉到畫面才需要用新的 API。

要放自訂資料,用 extensions

如果你需要在購物車或結帳的 API 回應裡帶自己的資料(例如「這張訂單符合免運資格」、「這個會員有多少點數」),對應的機制是 ExtendSchema:

use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;

add_action( 'woocommerce_blocks_loaded', function () {
	$extend = StoreApi::container()->get( ExtendSchema::class );

	$extend->register_endpoint_data(
		array(
			'endpoint'        => 'cart',            // 只能是有效的 Store API 端點.
			'namespace'       => 'my-plugin',       // 必填,你的外掛命名空間.
			'schema_callback' => function () {
				return array(
					'points_balance' => array(
						'description' => __( '會員點數餘額', 'my-plugin' ),
						'type'        => 'integer',
						'readonly'    => true,
					),
				);
			},
			'data_callback'   => function () {
				return array( 'points_balance' => (int) get_user_meta( get_current_user_id(), 'points', true ) );
			},
		)
	);
} );

註冊之後,你的資料會出現在 /cart 回應的 extensions 底下:

{ "extensions": { "my-plugin": { "points_balance": 350 } } }

三件要注意的事:

  1. namespace 是必填的,而且要用你的外掛前綴,這是避免跟其他外掛撞名的唯一保護
  2. endpoint 必須是有效端點,給錯會直接丟例外,訊息會列出所有合法值
  3. 要掛在 woocommerce_blocks_loaded 之後,太早呼叫拿不到容器

交給 AI 時最容易拿到的錯誤答案

這一篇的內容是整個第四部裡 AI 最容易答錯的地方。你問「怎麼在 WooCommerce 結帳頁加一個欄位」,十次有八次會拿到 woocommerce_checkout_fields 的寫法,那是舊版做法,網路上有上千篇文章這樣寫,而區塊結帳頁上它一點反應都沒有。

分辨方法很簡單,看它給的東西碰不碰得到畫面:

  • 只碰訂單、狀態、金流 → 舊做法多半仍然正確
  • 要改結帳頁的欄位、版面、驗證訊息 → 舊做法幾乎都無效,要用新的 API

還有一個實用的除錯習慣:遇到「改了沒反應」,先確認那個站的結帳頁到底是哪一種。打開結帳頁看原始碼,搜尋 wp-block-woocommerce-checkout 就是區塊版,看到 <form class="checkout woocommerce-checkout"> 就是傳統短代碼版。

明白資料怎麼流之後,下一篇我們用 WooCommerce 提供的 Additional Checkout Fields API,在區塊結帳頁加一個真正會存進訂單的自訂欄位。

AI 文章延伸

讓 AI 幫你讀這篇文章

選擇平台後會自動帶入閱讀脈絡,快速整理重點、補齊盲點,並延伸到同站相關文章。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料。