Skip to content

Instantly share code, notes, and snippets.

@Barnabas2
Last active May 3, 2026 00:23
Show Gist options
  • Select an option

  • Save Barnabas2/5b15e1540a509a4b6b1c56a4c5d4a13a to your computer and use it in GitHub Desktop.

Select an option

Save Barnabas2/5b15e1540a509a4b6b1c56a4c5d4a13a to your computer and use it in GitHub Desktop.
<?php
/**
* Custom Google Places address autocomplete provider for WooCommerce 10.3+.
* The snippet enables the Address autocomplete feature under Woocommerce -> Settings -> General
*
* Built to work alongside Stripe (by Payment Plugins) on Block-based checkout,
* since Woo's built-in admin toggle is gated to WooPayments only.
*
* Requires:
* - A Google Cloud project with Places API (New) enabled and billing active.
* - The API key restricted to your server's outbound IPv4 + IPv6 addresses.
* - A budget alert configured in Google Cloud Billing as a safety net.
*/
defined( 'ABSPATH' ) || exit;
/* -------------------------------------------------------------------------
* Configuration
* ------------------------------------------------------------------------- */
if ( ! defined( 'MYSTORE_ADDR_PROVIDER_ID' ) ) {
define( 'MYSTORE_ADDR_PROVIDER_ID', 'mystore-google-places' );
define( 'MYSTORE_ADDR_REST_NS', 'mystore-address/v1' );
define( 'MYSTORE_ADDR_GOOGLE_KEY', 'Google-Places-API-KEY' ); //👈 Add your API key here
}
/* -------------------------------------------------------------------------
* Force-enable the address autocomplete checkbox and select our provider
* as preferred, since Woo's admin UI gates these settings to WooPayments only.
* ------------------------------------------------------------------------- */
add_action( 'init', function () {
if ( get_option( 'woocommerce_address_autocomplete_enabled' ) !== 'yes' ) {
update_option( 'woocommerce_address_autocomplete_enabled', 'yes' );
}
if ( get_option( 'woocommerce_address_autocomplete_preferred_provider' ) !== MYSTORE_ADDR_PROVIDER_ID ) {
update_option( 'woocommerce_address_autocomplete_preferred_provider', MYSTORE_ADDR_PROVIDER_ID );
}
} );
/* -------------------------------------------------------------------------
* 1. Provider class — registered via woocommerce_address_providers filter.
* ------------------------------------------------------------------------- */
add_action( 'plugins_loaded', function () {
if ( ! class_exists( 'WC_Address_Provider' ) || class_exists( 'MyStore_Google_Places_Provider' ) ) {
return;
}
class MyStore_Google_Places_Provider extends WC_Address_Provider implements JsonSerializable {
public $id = 'mystore-google-places';
public $name = 'Google Places';
public $branding_html = '<div style="font-size:11px;color:#666;padding:4px 8px;">Powered by Google</div>';
public function __construct() {
$this->id = 'mystore-google-places';
$this->name = 'Google Places';
$this->branding_html = '<div style="font-size:11px;color:#666;padding:4px 8px;">Powered by Google</div>';
}
public function jsonSerialize() : array {
return [
'id' => $this->id,
'name' => $this->name,
'branding_html' => $this->branding_html,
];
}
}
}, 20 );
add_filter( 'woocommerce_address_providers', function ( $providers ) {
if ( class_exists( 'MyStore_Google_Places_Provider' ) ) {
$providers[] = new MyStore_Google_Places_Provider();
}
return $providers;
}, 10, 1 );
/* -------------------------------------------------------------------------
* 2. REST endpoints (search + details).
* ------------------------------------------------------------------------- */
add_action( 'rest_api_init', function () {
register_rest_route( MYSTORE_ADDR_REST_NS, '/address-search', [
'methods' => 'POST',
'callback' => 'mystore_addr_handle_search',
'permission_callback' => 'mystore_addr_check_nonce',
'args' => [
'query' => [ 'required' => true, 'type' => 'string' ],
'country' => [ 'required' => true, 'type' => 'string' ],
'type' => [ 'required' => false, 'type' => 'string' ],
],
] );
register_rest_route( MYSTORE_ADDR_REST_NS, '/address-details', [
'methods' => 'POST',
'callback' => 'mystore_addr_handle_details',
'permission_callback' => 'mystore_addr_check_nonce',
'args' => [
'place_id' => [ 'required' => true, 'type' => 'string' ],
],
] );
} );
function mystore_addr_check_nonce( WP_REST_Request $request ) {
$nonce = $request->get_header( 'x-wp-nonce' );
return (bool) wp_verify_nonce( $nonce, 'wp_rest' );
}
/**
* POST /address-search → returns a list of suggestions for the given query.
*/
function mystore_addr_handle_search( WP_REST_Request $request ) {
$query = sanitize_text_field( $request->get_param( 'query' ) );
$country = strtoupper( sanitize_text_field( $request->get_param( 'country' ) ) );
if ( strlen( $query ) < 3 ) {
return rest_ensure_response( [] );
}
$body = [
'input' => $query,
'includedRegionCodes' => [ $country ],
'languageCode' => substr( get_locale(), 0, 2 ),
];
$response = wp_remote_post( 'https://places.googleapis.com/v1/places:autocomplete', [
'timeout' => 8,
'headers' => [
'Content-Type' => 'application/json',
'X-Goog-Api-Key' => MYSTORE_ADDR_GOOGLE_KEY,
],
'body' => wp_json_encode( $body ),
] );
if ( is_wp_error( $response ) ) {
return rest_ensure_response( [] );
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$suggestions = [];
if ( ! empty( $data['suggestions'] ) ) {
foreach ( $data['suggestions'] as $s ) {
if ( empty( $s['placePrediction'] ) ) {
continue;
}
$pred = $s['placePrediction'];
$matches = [];
$matched = $pred['structuredFormat']['mainText']['matches'] ?? [];
foreach ( $matched as $m ) {
$start = (int) ( $m['startOffset'] ?? 0 );
$end = (int) ( $m['endOffset'] ?? 0 );
$matches[] = [
'offset' => $start,
'length' => $end - $start,
];
}
$suggestions[] = [
'id' => $pred['placeId'],
'label' => $pred['text']['text'] ?? '',
'matchedSubstrings' => $matches,
];
}
}
return rest_ensure_response( $suggestions );
}
/**
* POST /address-details → returns full address fields for the chosen place_id.
*/
function mystore_addr_handle_details( WP_REST_Request $request ) {
$place_id = sanitize_text_field( $request->get_param( 'place_id' ) );
if ( empty( $place_id ) ) {
return new WP_Error( 'no_place_id', 'Missing place_id', [ 'status' => 400 ] );
}
$response = wp_remote_get( 'https://places.googleapis.com/v1/places/' . rawurlencode( $place_id ), [
'timeout' => 8,
'headers' => [
'X-Goog-Api-Key' => MYSTORE_ADDR_GOOGLE_KEY,
'X-Goog-FieldMask' => 'addressComponents,formattedAddress',
],
] );
if ( is_wp_error( $response ) ) {
return new WP_Error( 'http_error', 'Lookup failed', [ 'status' => 502 ] );
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $data['addressComponents'] ) ) {
return new WP_Error( 'no_components', 'No address data', [ 'status' => 404 ] );
}
return rest_ensure_response( mystore_addr_parse_components( $data['addressComponents'] ) );
}
/**
* Map Google's address components to WooCommerce address fields.
*/
function mystore_addr_parse_components( array $components ) {
$get = function ( $type ) use ( $components ) {
foreach ( $components as $c ) {
if ( in_array( $type, $c['types'] ?? [], true ) ) {
return $c;
}
}
return null;
};
$street_number = ( $c = $get( 'street_number' ) ) ? ( $c['shortText'] ?? '' ) : '';
$route = ( $c = $get( 'route' ) ) ? ( $c['longText'] ?? '' ) : '';
$subpremise = ( $c = $get( 'subpremise' ) ) ? ( $c['longText'] ?? '' ) : '';
$city = '';
foreach ( [ 'postal_town', 'locality', 'sublocality_level_1', 'administrative_area_level_2' ] as $type ) {
if ( $c = $get( $type ) ) {
$city = $c['longText'] ?? '';
if ( $city ) {
break;
}
}
}
return [
'address_1' => trim( $street_number . ' ' . $route ),
'address_2' => $subpremise,
'city' => $city,
'state' => ( $c = $get( 'administrative_area_level_1' ) ) ? ( $c['shortText'] ?? '' ) : '',
'postcode' => ( $c = $get( 'postal_code' ) ) ? ( $c['longText'] ?? '' ) : '',
'country' => ( $c = $get( 'country' ) ) ? ( $c['shortText'] ?? '' ) : '',
];
}
/* -------------------------------------------------------------------------
* 3. Enqueue the client-side provider JS on checkout.
* Config is interpolated into the JS so there's no race with separate
* inline scripts.
* ------------------------------------------------------------------------- */
add_action( 'wp_enqueue_scripts', function () {
if ( ! function_exists( 'is_checkout' ) || ! is_checkout() ) {
return;
}
if ( get_option( 'woocommerce_address_autocomplete_enabled' ) !== 'yes' ) {
return;
}
$cfg_json = wp_json_encode( [
'restUrl' => esc_url_raw( rest_url( MYSTORE_ADDR_REST_NS . '/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
] );
$provider_js = <<<JS
( function () {
'use strict';
const cfg = {$cfg_json};
const post = async ( endpoint, payload ) => {
const res = await fetch( cfg.restUrl + endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': cfg.nonce,
},
body: JSON.stringify( payload ),
} );
if ( ! res.ok ) {
throw new Error( 'Request failed: ' + res.status );
}
return res.json();
};
const provider = {
id: 'mystore-google-places',
canSearch: function ( country ) {
return Boolean( country );
},
search: async function ( query, country, type ) {
try {
const results = await post( 'address-search', { query, country, type } );
return Array.isArray( results ) ? results : [];
} catch ( e ) {
return [];
}
},
select: async function ( addressId ) {
try {
return await post( 'address-details', { place_id: addressId } );
} catch ( e ) {
return null;
}
},
};
const register = () => {
if (
window.wc &&
window.wc.addressAutocomplete &&
window.wc.addressAutocomplete.registerAddressAutocompleteProvider
) {
try {
window.wc.addressAutocomplete.registerAddressAutocompleteProvider( provider );
return true;
} catch ( e ) {
return false;
}
}
return false;
};
if ( register() ) return;
document.addEventListener( 'DOMContentLoaded', register );
window.addEventListener( 'load', register );
let tries = 0;
const t = setInterval( function () {
if ( register() || ++tries > 300 ) {
clearInterval( t );
}
}, 200 );
} )();
JS;
wp_register_script( 'mystore-address-provider', '', [ 'wc-address-autocomplete' ], '1.0', true );
wp_enqueue_script( 'mystore-address-provider' );
wp_add_inline_script( 'mystore-address-provider', $provider_js, 'after' );
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment