Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/ryansallen98/saipan-location-checkout/archive/refs/heads/main.zipA WooCommerce plugin that replaces traditional address entry with an interactive map pin drop at checkout — built specifically for Saipan, where many residents don't have a conventional street address.
How it works
When a customer selects the Saipan Delivery shipping method, a Leaflet map appears on the checkout page. They click (or drag) to drop a pin at their delivery location. The coordinates are saved to the order and displayed in the WooCommerce admin with a direct link to Google Maps.
Saipan Delivery pricing: In WooCommerce → Settings → Shipping → your zone → Saipan Delivery, choose Flat fee or Distance (per mile). Distance charges straight-line miles from your store map pin (under WooCommerce → Settings → General, below Store Address) to the customer’s delivery pin. Set Cost per mile and optionally Minimum delivery fee (e.g. $1/mi with a $5 minimum: 3 mi → $5, 7 mi → $7).
When they select Saipan Local Pickup, they see a read-only map with a pin at the pickup location for that rate (no street address required), plus View on Google Maps. Pickup coordinates are saved on the order separately for admin reference.
Default pickup location: Under WooCommerce → Settings → General, below Store Address, set Store location on map — this is the default pin for new Saipan Local Pickup methods and the fallback when a zone method has no valid coordinates. Override any zone by changing the map in WooCommerce → Settings → Shipping for that method instance.
Cart vs checkout shipping: The plugin can hide all shipping calculation on the cart (including block cart when the browser sends a matching cart-page Referer) so costs only appear at checkout. This is on by default; use the saipan_location_checkout_suppress_cart_shipping filter (return false) if you want WooCommerce’s default cart shipping again.
The method declares WooCommerce’s local-pickup feature support, so on block checkout it is grouped under Collection (local pickup) with core pickup options—not under delivery.
Block checkout — Ship / Pickup tabs: WooCommerce also uses a master flag from WooCommerce → Settings → Shipping → Local pickup (localPickupEnabled in block settings). If that is left disabled, the Collection tab does not appear even when collectible methods exist. This plugin sets localPickupEnabled on the checkout page when at least one zone has Saipan Local Pickup enabled, so the toggle shows without forcing you to turn on core “Local pickup” locations. If your site redirects /checkout/ to the cart and checkout runs inside the block cart/checkout flow, the same patch is output on the cart page (when block checkout scripts load) and retries until collectible method IDs are available. You can still enable core Local pickup if you want that feature too; to opt out of our patch, use the saipan_location_checkout_force_block_collection_tab filter (return false).
The plugin supports both checkout types:
| Checkout type | How it's integrated |
|---|---|
Block checkout (woocommerce/checkout block) |
React component registered via the WooCommerce Blocks Checkout API. Coordinates are passed through the Store API extension data. |
Classic checkout ([woocommerce_checkout] shortcode) |
Vanilla JS + Leaflet injected into the review order section. Coordinates are submitted as hidden form fields. |
Delivery coordinates use the same order meta keys for both checkout types (saipan_location_latitude, saipan_location_longitude). Local pickup uses saipan_pickup_latitude and saipan_pickup_longitude.
Requirements
Runtime minimums are declared in the plugin header (saipan-location-checkout.php) and enforced by WordPress on install:
- WordPress 6.4+
- PHP 8.0+
- WooCommerce 7.6+ (block checkout, Store API extensions, and shipping integration)
Development tooling:
- Node 18+ / npm 9+
- Composer 2+
Installation
- Upload the plugin folder to
wp-content/plugins/ - Activate via Plugins → Installed Plugins
- Go to WooCommerce → Settings → Shipping and add Saipan Delivery and/or Saipan Local Pickup to a shipping zone
- For Saipan Delivery, set the flat fee. For Saipan Local Pickup, set tax/cost if needed and place the store pin on the map in the method settings, then save
Project structure
saipan-location-checkout/
├── saipan-location-checkout.php Main plugin file — registers hooks, loads classes
│
├── includes/
│ ├── class-shipping-method.php WooCommerce **Saipan Delivery** (flat rate, zone-based)
│ ├── class-shipping-method-local-pickup.php **Saipan Local Pickup** (map-based pickup point)
│ ├── class-pickup-rate-locations.php Resolves pickup coordinates for checkout + orders
│ ├── class-classic-checkout.php Classic checkout integration (PHP hooks + asset loading)
│ ├── class-tile-downloader.php Tile coordinate maths, batch download, status, removal
│ ├── class-admin.php Admin tile-management page + AJAX handlers
│ ├── class-github-updater.php GitHub release update checker
│ └── block/
│ ├── class-store-api.php Extends the WooCommerce Store API checkout schema
│ ├── class-woo-core.php Saves block checkout coordinates + admin order display
│ └── class-integration.php Registers block JS/CSS with WooCommerce Blocks
│
├── templates/
│ ├── admin/
│ │ └── tiles-page.php Admin tile management page markup
│ └── frontend/
│ └── classic-checkout-map.php Classic checkout map picker markup
│
├── src/
│ ├── index.ts Registers the plugin with WooCommerce's checkout scope
│ ├── types/
│ │ ├── globals.d.ts window.saipanLocationCheckout / saipanAdminTiles globals
│ │ ├── assets.d.ts Webpack image import declarations
│ │ └── woocommerce.d.ts Minimal @woocommerce/blocks-checkout type stubs
│ ├── admin/
│ │ ├── tiles.ts Admin tile download UI (AJAX batching, progress, ETA)
│ │ └── __tests__/
│ │ └── tiles.test.ts Jest tests for formatEta()
│ ├── block/ Block checkout — React + react-leaflet
│ │ ├── block.json Block metadata (name, parent block, attributes)
│ │ ├── index.ts Registers the checkout block
│ │ ├── block.tsx Main React component (map, validation, geolocation)
│ │ ├── components/
│ │ │ └── WooCordInput.tsx Read-only coordinate display field
│ │ └── hooks/
│ │ └── useIsCollectionSelected.ts Detects Ship vs Local Pickup tab
│ └── classic/
│ └── index.ts Classic checkout — vanilla JS + Leaflet
│
├── tests/
│ └── php/
│ ├── bootstrap.php PHPUnit bootstrap — stubs WP functions, loads class
│ ├── AdminTest.php Admin / AJAX-related PHPUnit tests
│ ├── ClassicCheckoutTest.php Classic checkout PHPUnit tests
│ └── TileDownloaderTest.php PHPUnit tests for Saipan_Location_Tile_Downloader
│
├── docs/
│ └── INDEX.md Canonical documentation index (architecture pointers, logging, incident queues)
│
├── scripts/
│ └── setup.sh One-command dev environment setup
│
├── composer.json PHP dev dependencies (PHPCS, WPCS, PHPUnit)
├── phpcs.xml PHP_CodeSniffer ruleset (WordPress Coding Standards)
├── phpunit.xml PHPUnit configuration
│
└── build/ Compiled output (committed, do not edit manually)
├── admin/ Admin assets (tiles.js)
├── block/ Block checkout assets
└── classic/ Classic checkout assets
Development
First-time setup
npm run setup
This installs all Node dependencies (npm install) and all Composer dependencies (composer install) in one step.
Build
npm run build
Before compiling, the build automatically runs all linters and test suites. The build only proceeds if every check passes. Individual checks:
| Command | What it runs |
|---|---|
npm run lint:js |
ESLint (WordPress + TypeScript rules) |
npm run lint:php |
PHP_CodeSniffer with WordPress Coding Standards |
npm run test:js |
Jest unit tests (src/**/__tests__/) |
npm run test:php |
PHPUnit unit tests (tests/php/) |
npm run lint:php:fix |
PHPCBF auto-fixer for PHP style violations |
npm run typecheck |
TypeScript type check without emitting |
Development build with file watching
npm start
Watch mode compiles on change and does not run the lint/test gate — use it during active development.
Map tiles
The checkout map can serve tiles from the OpenStreetMap CDN (default, no setup) or from your own server (faster, no CDN dependency).
To self-host tiles go to WooCommerce → Saipan Tiles in the WordPress admin:
- Tiles are downloaded in batches of 50 via AJAX — the operation never hits PHP's
max_execution_time. - A live progress bar shows download percentage and estimated time remaining.
- Tiles are stored in
wp-content/uploads/saipan-tiles/and survive plugin updates. - The Remove Tiles button deletes the local cache; the map falls back to the OSM CDN automatically.
Coverage: Saipan at zoom levels 12–17 (~50 MB, ~9,200 tiles).
Map details
- Library: Leaflet 1.9.4
- Tiles: OpenStreetMap (CDN fallback) or self-hosted via uploads directory
- Centre: 15.18°N, 145.75°E
- Bounds: Restricted to Saipan (
[[15.09, 145.64], [15.29, 145.88]]) - Zoom: 13 default, 12 min, 17 max
Order data
Coordinates are stored as order meta and visible in the WooCommerce admin:
| Meta key | When set | Example value |
|---|---|---|
saipan_location_latitude |
Saipan Delivery | 15.212345 |
saipan_location_longitude |
Saipan Delivery | 145.754321 |
saipan_pickup_latitude |
Saipan Local Pickup | 15.212345 |
saipan_pickup_longitude |
Saipan Local Pickup | 145.754321 |
A View on Google Maps button is shown for delivery and pickup location sections on the order screen.