Spring til indhold

Guide

Adresse-autocomplete i WooCommerce

Danske adresseforslag i din WooCommerce-checkout, med nøglen sikkert på serveren. WooCommerce har to forskellige checkouts, og de kræver ikke den samme kode — det er den detalje, de fleste vejledninger springer over. Her er begge.

Find først ud af, hvilken checkout du har

Den klassiske checkout er [woocommerce_checkout]-shortcoden: almindelige HTML-felter med kendte id'er. Checkout-blokken er renderet i React og har hverken de samme id'er eller den samme opførsel. Nyere butikker er typisk på blokken.

hvilken checkout?typescript
// Er butikken på blok-checkout eller klassisk checkout?
// Blokken efterlader en wrapper med denne klasse; den klassiske gør ikke.
//
// WooCommerce betragter selv klassenavne inde i blokke som private og
// forbeholder sig at ændre dem. Til en engangsafgørelse som denne er det
// i praksis den nemmeste vej — men lad være med at bygge styling oven på
// blokkens interne klasser, og tjek den her linje, hvis feltet en dag
// holder op med at binde efter en WooCommerce-opdatering.
const erBlok = !!document.querySelector('.wp-block-woocommerce-checkout');

1. Proxy i WordPress — så nøglen bliver på serveren

Ens for begge checkouts. En API-nøgle i temaets JavaScript kan læses af enhver besøgende og bruges på din regning, så kaldet går gennem din egen REST-rute.

functions.phptext
<?php
// functions.php (eller et lille must-use plugin).
// Læg nøglen i wp-config.php: define('GRUNDFAST_KEY', 'gf_live_...');

add_action('rest_api_init', function () {
  register_rest_route('grundfast/v1', '/adresse', [
    'methods'             => 'GET',
    'permission_callback' => '__return_true', // offentligt checkout-felt
    'callback'            => function (WP_REST_Request $req) {
      $q = trim((string) $req->get_param('q'));
      if (mb_strlen($q) < 2) {
        return ['results' => []];
      }

      $res = wp_remote_get(
        'https://api.grundfast.dk/v1/adresse/search?per_side=8&q=' . rawurlencode($q),
        [
          'headers' => ['Authorization' => 'Bearer ' . GRUNDFAST_KEY],
          'timeout' => 5,
        ]
      );
      if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) {
        return ['results' => []]; // fejl må aldrig blokere et checkout
      }

      return json_decode(wp_remote_retrieve_body($res), true);
    },
  ]);
});

// Indlæs feltet kun på checkout-siden.
add_action('wp_enqueue_scripts', function () {
  if (!function_exists('is_checkout') || !is_checkout()) {
    return;
  }
  wp_enqueue_script(
    'grundfast-adresse',
    get_stylesheet_directory_uri() . '/js/grundfast-adresse.js',
    [],
    '1.0.0',
    true
  );
  wp_localize_script('grundfast-adresse', 'GrundfastCfg', [
    'endpoint' => rest_url('grundfast/v1/adresse'),
  ]);
});

2a. Klassisk checkoutdirekte i felterne

Felterne er almindelig HTML, så du kan skrive direkte i dem. Husk at bede WooCommerce genberegne fragten, når postnummeret ændrer sig.

grundfast-adresse.jstypescript
// Klassisk checkout: felterne er almindelig HTML, så du kan skrive
// direkte i dem. Feltet du skriver i er #billing_address_1.
const input = document.querySelector('#billing_address_1');
let timer, inflight;

input?.addEventListener('input', () => {
  clearTimeout(timer);
  timer = setTimeout(async () => {
    const q = input.value.trim();
    if (q.length < 2) return;

    inflight?.abort();
    inflight = new AbortController();
    const res = await fetch(
      GrundfastCfg.endpoint + '?q=' + encodeURIComponent(q),
      { signal: inflight.signal }
    );
    const { results } = await res.json();

    // Tegn din egen forslagsliste her; ved valg af 'r':
    const vaelg = (r) => {
      input.value = [r.vejnavn, r.husnr].filter(Boolean).join(' ');
      document.querySelector('#billing_postcode').value = r.postnr ?? '';
      document.querySelector('#billing_city').value = r.postnrnavn ?? '';
      // Lad WooCommerce genberegne fragt for det nye postnummer.
      jQuery(document.body).trigger('update_checkout');
    };
    void vaelg;
  }, 150);
});

2b. Checkout-blokken.value virker ikke

Her er fælden. Blokkens felter er React-styrede: sætter du input.value direkte, ser du teksten et øjeblik, men React overskriver den ved næste render — og WooCommerce sender den gamle værdi med ordren. Det er en fejl, der først viser sig, når pakkerne kommer retur. Skriv til datalageret i stedet, så opdaterer blokken felterne selv.

grundfast-adresse.jstypescript
// Checkout-blokken: felterne er React-styrede. At sætte .value
// direkte bliver IKKE opfanget — React overskriver det ved næste render,
// og WooCommerce sender stadig den gamle værdi med ordren.
// Skriv i stedet til datalageret, så bliver felterne opdateret for dig.
const { dispatch } = window.wp.data;

function vaelgAdresse(r) {
  const adresse = {
    address_1: [r.vejnavn, r.husnr].filter(Boolean).join(' '),
    postcode: r.postnr ?? '',
    city: r.postnrnavn ?? '',
    country: 'DK',
  };

  // Bemærk: set*Address sætter adressen LOKALT i browserens datalager.
  // Det er det, du vil have her — kunden er i gang med at udfylde, og
  // værdierne følger med, når blokken selv sender kurven af sted.
  // Skal den skrives til serveren med det samme (fx en "gem adresse"-
  // knap uden for checkout), er updateCustomerData den, der gør det.
  dispatch('wc/store/cart').setShippingAddress(adresse);
  // Kun hvis kunden ikke har slået separat faktureringsadresse til:
  dispatch('wc/store/cart').setBillingAddress(adresse);
}

Blokkens API-flade er stadig i bevægelse på tværs af WooCommerce-versioner. Test mod netop din version, og læg en ordre igennem til ende — kontrollér at adressen på den færdige ordre er den, kunden valgte, ikke bare den, feltet viste.

Byg det på en test-nøgle

En gf_test_-nøgle giver rigtige svar, faktureres aldrig og har 25.000 kald/md — rigeligt til at få begge checkouts på plads.

Se også

Ofte stillede spørgsmål

Virker det både på klassisk checkout og checkout-blokken?

Ja, men ikke med samme kode. Klassisk checkout ([woocommerce_checkout]-shortcoden) er almindelig HTML, hvor du kan skrive direkte i #billing_address_1, #billing_postcode og #billing_city. Checkout-blokken er renderet i React, og der bliver en direkte .value-tildeling ikke opfanget — du skal skrive til wc/store/cart-datalageret i stedet. Guiden viser begge veje.

Hvorfor virker min JavaScript ikke på checkout-blokken?

Fordi felterne er React-styrede. Sætter du input.value direkte, ser du teksten et øjeblik, men React overskriver den ved næste render, og WooCommerce sender den gamle værdi med ordren. Det er den klassiske fælde. Brug wp.data.dispatch("wc/store/cart").setShippingAddress({...}) i stedet. Bemærk at blokkens API-flade stadig bevæger sig — test mod din egen WooCommerce-version.

Skal jeg bruge et plugin?

Nej. En REST-rute i functions.php (eller et lille must-use plugin) og én JavaScript-fil er nok. Pointen med proxy-ruten er, at API-nøglen bliver på serveren i stedet for at ligge læsbar i browseren.

Hvorfor skal fragtberegningen opdateres?

Fordi WooCommerce beregner fragt ud fra postnummeret. Udfylder du postnummeret programmatisk uden at give besked, kan kunden se en fragtpris, der hører til den forrige adresse. På klassisk checkout trigger du update_checkout; på blokken sker det af sig selv, når du skriver til datalageret.

Hvad med de ordrer, jeg allerede har liggende?

Et nyt felt retter kun fremtidige ordrer. De eksisterende adresser kan vaskes mod DAR med /v1/datavask/adgangsadresser, som giver rangerede kandidater med A/B/C-konfidensbånd og bfe_nummer — så kan det sikre rettes automatisk og resten sendes til gennemsyn.