Files
playbook/antigravity-awesome-skills/skills/woo-guard/references/checkout-and-money.md
T
2026-07-18 00:02:59 +00:00

3.5 KiB

Woo Guard — Checkout and Money Reference

Contents

  • Two checkouts, two hook surfaces
  • Server-side validation
  • Cart and session context guards
  • Money handling
  • Gateway and webhook callbacks

Two checkouts, two hook surfaces

Modern stores run the Blocks checkout (Store API); legacy stores run the shortcode checkout (woocommerce_checkout_* hooks, wp_ajax fragments). The hook surfaces do not overlap:

  • Legacy-only: woocommerce_checkout_fields, woocommerce_checkout_process, woocommerce_after_order_notes.
  • Blocks: Store API extensions (ExtendSchema), additional-fields API, server-side endpoint validation.

An extension claiming general compatibility wires both or declares which one it supports (see compatibility declaration in hpos-and-crud.md). AI-generated checkout code overwhelmingly targets the legacy surface only — on a Blocks store it simply never runs: a hook on the wrong surface fails silently, no error, no behavior.

Server-side validation

/**
 * Reject checkout when the VAT number is malformed.
 *
 * JS validation on the field is UX; this hook is the actual gate.
 */
add_action( 'woocommerce_checkout_process', function () {
	$vat = isset( $_POST['ncs_vat'] ) ? sanitize_text_field( wp_unslash( $_POST['ncs_vat'] ) ) : '';

	if ( '' !== $vat && ! ncs_vat_is_valid( $vat ) ) {
		wc_add_notice( __( 'Please enter a valid VAT number.', 'ncs-checkout' ), 'error' );
	}
} );

For Blocks, the equivalent lives in the Store API additional-fields/extension schema callbacks. Either way: the server decides, every field is unslashed then sanitized with the type-correct function, and error messages go through wc_add_notice()/schema errors — never die().

Cart and session context guards

WC()->cart, WC()->session, and WC()->customer are initialized for front-end requests — they are null in REST, cron, CLI, webhooks, and most admin requests:

if ( function_exists( 'WC' ) && WC()->cart instanceof WC_Cart ) {
	$count = WC()->cart->get_cart_contents_count();
}

Code that touches the cart inside an API callback or scheduled job is a fatal error wearing a demo-store disguise. Flag it in review even when "it worked locally."

Money handling

  • Storage and arithmetic inputs: wc_format_decimal( $value ) — normalizes locale decimals and precision.
  • Display: wc_price( $amount ) — currency symbol, position, separators, all from store settings. Hardcoded '$' . $amount fails on the other 150 currencies.
  • Totals and tax: use order/cart getters (get_total(), get_subtotal(), WC_Tax methods) — they apply the store's rounding mode. Re-deriving totals with raw float math produces penny drift that accountants will find.
  • Comparisons: compare formatted decimals or integer minor units; never == on floats.
  • Refund/discount logic: negative amounts have meaning — test the zero and partial cases explicitly (test-guard says hi).

Gateway and webhook callbacks

  • Verify webhook signatures/secrets before touching any order; fail closed with the provider's expected status code.
  • Look up orders from gateway references via wc_get_orders( array( 'transaction_id' => … ) ) or stored CRUD meta — not custom SQL.
  • Callbacks run unauthenticated by design: capability checks don't apply, signature verification is the authentication, and every input is still unslashed and sanitized before use.
  • Idempotency: providers retry. Processing the same payment event twice must not complete an order twice.