# CEP Autofill for WooCommerce Fills the WooCommerce checkout address from the CEP the shopper types. Street, neighbourhood, city and state, from official Correios data (DNE) via the [public API at cep.is](https://cep.is). Works on **both** WooCommerce checkouts — the Cart/Checkout Blocks and the classic `[woocommerce_checkout]` shortcode — see [Supported checkouts](#supported-checkouts). No Composer, no npm, no build step: the PHP client and the JavaScript widget are bundled in the plugin. ## Install Download the `.zip` from `dist/` (or run `make build`), then **Plugins → Add New → Upload Plugin** in WordPress. Activate it; WooCommerce must be active first. By default the shopper's browser talks to cep.is directly, so there is nothing to configure and no key to obtain. Requires WordPress 6.9 or newer, PHP 8.1 or newer, and WooCommerce. ## Settings **WooCommerce → Settings → CEP autofill.** | setting | default | what it does | | -------------------------- | -------- | ----------------------------------------------------------------------------------- | | **Autofill** | on | look the address up while the shopper types | | **Lookups go through** | browser | the browser straight to cep.is, or this site's REST API | | **Fields already filled** | keep | keep what the shopper typed / only fill empty fields / always overwrite | | **House number** | on | move the cursor to the house-number field after a hit (classic checkout) | | **API address** | cep.is | point at your own instance of the API | ### Lookups go through **The browser (default).** One cross-origin GET against a Cloudflare edge cache. No PHP runs, nothing is billed to the store, and the answer arrives in about twenty milliseconds. **This site.** The browser calls `/wp-json/cepis/v1/lookup/{token}/{cep}` instead, and PHP calls cep.is through `wp_remote_get()`. Choose this when a Content-Security-Policy on the checkout forbids external requests, or when you want the answers cached server-side. Answers are kept in transients: a hit for a week, since the database behind the API is regenerated fortnightly and a week is half a refresh cycle; a "does not exist" for a day, since a CEP absent today can appear in the next release. The route carries a short-lived token derived from the site's salts, and answers `403` without one. It is registered only while this mode is on. ### Fields already filled in `keep` — the default — fills empty fields, replaces values this plugin wrote itself, and leaves anything the shopper typed alone, so correcting a street by hand survives the next CEP. The state is the exception and is always replaced: the CEP decides it, and WooCommerce pre-selects the store's own state on a fresh checkout. ## Supported checkouts Both, and both verified in a real browser against a real store. | checkout | supported | how | | ---------------------------------------------- | --------- | --------------------------------------------------------------------------- | | **Cart/Checkout Blocks** (the default since WooCommerce 8.3) | yes | reads and writes the `wc/store/cart` data store; no DOM is touched | | **Classic** `[woocommerce_checkout]` | yes | the widget fills the fields directly and announces progress in an `aria-live` region | On the block checkout the fields are React-controlled, so the plugin drives the official `setShippingAddress` / `setBillingAddress` actions and reports failures through the checkout's own notice area. On the classic checkout the status message appears under the CEP field, styleable through `.cepis-autofill-status[data-cepis-state="…"]`. Both fill billing and shipping. The cart block's shipping calculator is not covered. ## When the CEP does not exist A CEP that fails at the checkout is rarely a random number. It is usually a shopper who knows perfectly well which town they live in and got a digit wrong — most often by trying the `-000`, which 957 municipalities never received. Areal/RJ, for one, answers from 25845-001 upwards and never at 25845-000. When the API says which municipality the number would have belonged to, so does the message: > CEP not found. This number would be in Areal/RJ, which has CEPs from > 25845-001 to 25849-899. Check the number. It is still an **error**. Nothing from it is ever written into a checkout field — not the city, not the state, nothing at all. A CEP that does not exist has no address, and filling one in from the municipality behind it means a parcel delivered to the wrong town. The two CEPs quoted are the lowest and the highest that really exist there, not a list of valid ones and not a suggestion. The range is sparse — 118 real CEPs across nearly five thousand numbers, in Areal's case — so a number picked out of the middle of it would most likely not exist either. When the API names no municipality, because the CEP falls in no range at all, the message is the one it has always been. Reading it costs one extra request, on the miss path only and once per CEP. Both checkouts do it and both lookup modes support it: with **This site** selected the municipality is passed through with the 404 and cached with it. Verified on WordPress 7.0.2, WooCommerce 10.9.4, PHP 8.3. `Requires at least` is declared as 6.9 because that is what WooCommerce 10.x itself requires; older WordPress was not tested. ## Brazilian address fields WooCommerce has no house-number and no neighbourhood field. When [Brazilian Market on WooCommerce](https://wordpress.org/plugins/woocommerce-extra-checkout-fields-for-brazil/) adds `billing_number` and `billing_neighborhood` to the classic checkout, this plugin fills the neighbourhood and moves the cursor to the number. When they are absent it fills what exists and does nothing else — the neighbourhood is **not** written into `address_2`, which in WooCommerce means "apartment, suite, unit" and would produce a wrong shipping label. The block checkout has no such fields and no plugin currently adds them. A store that registers its own with `woocommerce_register_additional_checkout_field()` can map it: ```php add_filter( 'cepis_woocommerce_blocks_fields', function ( $fields ) { $fields['bairro'] = 'my-plugin/neighborhood'; return $fields; } ); ``` The classic equivalent is `cepis_woocommerce_classic_fields`, which takes CSS selectors per address section, for themes and one-page-checkout plugins that rename the fields. `cepis_woocommerce_messages` overrides what the shopper is told. ## Development ```sh make # list the targets make test # stand a store up in Docker and drive the checkout in Chromium make lint # php -l, prettier --check, and phpcs when installed make format # apply prettier (writes) make build # produce dist/cepis-woocommerce-0.2.0.zip make check # lint, then test, then build make clean # remove build output and throw the test containers away ``` `make test` needs Docker and `bun`. It brings up WordPress, MySQL and WP-CLI from `test/compose.yml`, installs and configures WooCommerce, and drives both checkouts with Playwright's Chromium against a fake cep.is on the host, so no test touches the network. Install Chromium with `bunx playwright install chromium`, or point `CEPIS_CHROMIUM` at an existing binary. `make phpcs` fetches PHP_CodeSniffer and the WordPress standard into `.phpcs/`; `make lint` picks them up automatically once they are there. ### Refreshing the bundled dependencies `lib/cepis/` is the [PHP client](https://cep.is) and `assets/js/cepis-autofill.min.js` is the [autofill widget](https://cep.is), both copied in rather than installed, because a WordPress site is deployed by copying files and never runs `composer install`. ```sh tools/update-vendor.sh ../cepis-php ../cepis-autofill ``` That rebuilds the widget, copies both in, and leaves you a diff to review. ## Licence ISC. See [LICENSE](LICENSE).