Small fixes + Postcodelookup fallback

This commit is contained in:
Mark Pinkster 2026-01-11 17:52:50 +01:00
parent f05152c626
commit bf84540401
3 changed files with 163 additions and 27 deletions

View File

@ -1,12 +1,14 @@
<?php
/**
* Postcode Service - Handles postcode lookup via postcode.tech API
* with fallback to openpostcode.nl API
*/
class PostcodeService
{
/**
* Lookup address by postcode and house number
* Uses postcode.tech as primary, falls back to openpostcode.nl
*
* @param string $postcode
* @param string $number
@ -14,7 +16,7 @@ class PostcodeService
*/
public static function lookup(string $postcode, string $number): array
{
$postcode = str_replace(' ', '', $postcode);
$postcode = strtoupper(str_replace(' ', '', $postcode));
if (empty($postcode) || empty($number)) {
return [
@ -24,6 +26,26 @@ class PostcodeService
];
}
// Try primary service (postcode.tech)
$result = self::lookupPostcodeTech($postcode, $number);
// If primary service fails (server error or timeout), try fallback
if (!$result['success'] && $result['http_code'] >= 500) {
$result = self::lookupOpenPostcode($postcode, $number);
}
return $result;
}
/**
* Lookup via postcode.tech API (primary)
*
* @param string $postcode
* @param string $number
* @return array
*/
private static function lookupPostcodeTech(string $postcode, string $number): array
{
$url = "https://postcode.tech/api/v1/postcode?postcode={$postcode}&number={$number}";
$ch = curl_init($url);
@ -42,21 +64,16 @@ class PostcodeService
if ($curlError || $httpCode >= 500) {
return [
'success' => false,
'error' => 'Postcode service niet bereikbaar, vul straat en woonplaats zelf in',
'error' => 'Postcode.tech service niet bereikbaar',
'details' => $curlError,
'http_code' => 503
];
}
if ($httpCode >= 400) {
$decoded = json_decode($response, true);
$errorMessage = ($decoded && isset($decoded['error']))
? $decoded['error']
: 'Postcode niet gevonden of ongeldige invoer, vul straat en woonplaats zelf in';
return [
'success' => false,
'error' => $errorMessage,
'error' => self::translateErrorMessage($response, $httpCode),
'http_code' => $httpCode
];
}
@ -69,4 +86,123 @@ class PostcodeService
'http_code' => 200
];
}
/**
* Lookup via openpostcode.nl API (fallback)
* No authentication required
*
* @param string $postcode
* @param string $number
* @return array
*/
private static function lookupOpenPostcode(string $postcode, string $number): array
{
$url = "https://openpostcode.nl/api/address?postcode={$postcode}&huisnummer={$number}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError || $httpCode >= 500) {
return [
'success' => false,
'error' => 'Postcode service niet bereikbaar, vul straat en woonplaats zelf in',
'details' => $curlError,
'http_code' => 503
];
}
if ($httpCode >= 400) {
return [
'success' => false,
'error' => self::translateErrorMessage($response, $httpCode),
'http_code' => $httpCode
];
}
$data = json_decode($response, true);
// Transform openpostcode.nl response to match postcode.tech format
$transformedData = self::transformOpenPostcodeResponse($data);
return [
'success' => true,
'data' => $transformedData,
'http_code' => 200
];
}
/**
* Transform openpostcode.nl response to match postcode.tech format
*
* openpostcode.nl returns:
* - postcode, huisnummer, straat, buurt, wijk, woonplaats, gemeente, provincie, latitude, longitude
*
* postcode.tech returns:
* - postcode, number, street, city, municipality, province, geo (lat, lng)
*
* @param array $data
* @return array
*/
private static function transformOpenPostcodeResponse(array $data): array
{
return [
'postcode' => $data['postcode'] ?? '',
'number' => $data['huisnummer'] ?? '',
'street' => $data['straat'] ?? '',
'city' => $data['woonplaats'] ?? '',
'municipality' => $data['gemeente'] ?? '',
'province' => $data['provincie'] ?? '',
'geo' => [
'lat' => $data['latitude'] ?? null,
'lng' => $data['longitude'] ?? null
]
];
}
/**
* Translate API error messages to user-friendly Dutch messages
*
* @param string $response Raw API response
* @param int $httpCode HTTP status code
* @return string User-friendly error message in Dutch
*/
private static function translateErrorMessage(string $response, int $httpCode): string
{
$decoded = json_decode($response, true);
$apiError = ($decoded && isset($decoded['error'])) ? strtolower($decoded['error']) : '';
// Map common API error messages to Dutch user-friendly messages
if (strpos($apiError, 'huisnummer not found') !== false ||
strpos($apiError, 'house number not found') !== false ||
strpos($apiError, 'number not found') !== false) {
return 'Dit huisnummer is niet gevonden bij deze postcode. Controleer het huisnummer of vul het adres handmatig in.';
}
if (strpos($apiError, 'postcode not found') !== false ||
strpos($apiError, 'invalid postcode') !== false) {
return 'Deze postcode is niet gevonden. Controleer de postcode of vul het adres handmatig in.';
}
if (strpos($apiError, 'invalid') !== false) {
return 'Ongeldige invoer. Controleer de postcode en het huisnummer.';
}
if ($httpCode === 404) {
return 'Adres niet gevonden. Controleer de postcode en het huisnummer of vul het adres handmatig in.';
}
if ($httpCode === 429) {
return 'Te veel verzoeken. Probeer het later opnieuw of vul het adres handmatig in.';
}
// Default message
return 'Adres niet gevonden. Vul straat en woonplaats handmatig in.';
}
}

View File

@ -200,10 +200,25 @@
</template>
</select>
</div>
<div x-show="recommendedOptions.length > 0" x-cloak class="mt-8 space-y-3">
<p class="text-[10px] font-black text-red-500 uppercase tracking-widest italic px-2 text-center">
Wellicht ook interessant</p>
<template x-for="u in recommendedOptions" :key="u.id">
<div
class="flex items-center justify-between p-4 border rounded-2xl bg-slate-50 hover:bg-white transition-all shadow-sm">
<span class="text-xs font-bold text-slate-700"
x-text="u.name + ' (€' + u.price + ')'"></span>
<button @click="toggleUpsell(u)" :class="isInCart(u.id) ? 'bg-red-500' : 'bg-green-600'"
class="text-white px-6 py-2 rounded-xl text-[10px] font-black uppercase shadow-md"
x-text="isInCart(u.id) ? 'Verwijder' : 'Voeg toe'">
</button>
</div>
</template>
</div>
<div class="mt-8 border-t pt-6">
<h2 class="font-bold mb-4 text-slate-400 uppercase text-[10px] tracking-widest italic text-center">
Extra's</h2>
Voeg extra product toe</h2>
<div class="flex gap-2">
<!-- Custom searchable dropdown for extra products -->
<div class="relative flex-1" x-data="{ openExtra: false }" @click.away="openExtra = false">
@ -244,22 +259,7 @@
</div>
</div>
<div x-show="recommendedOptions.length > 0" x-cloak class="mt-8 space-y-3">
<p class="text-[10px] font-black text-red-500 uppercase tracking-widest italic px-2 text-center">
Aanbevolen</p>
<template x-for="u in recommendedOptions" :key="u.id">
<div
class="flex items-center justify-between p-4 border rounded-2xl bg-slate-50 hover:bg-white transition-all shadow-sm">
<span class="text-xs font-bold text-slate-700"
x-text="u.name + ' (€' + u.price + ')'"></span>
<button @click="toggleUpsell(u)" :class="isInCart(u.id) ? 'bg-red-500' : 'bg-green-600'"
class="text-white px-6 py-2 rounded-xl text-[10px] font-black uppercase shadow-md"
x-text="isInCart(u.id) ? 'Verwijder' : 'Voeg toe'">
</button>
</div>
</template>
</div>
</div>
<div class="col-span-12 lg:col-span-3">

View File

@ -58,8 +58,8 @@ const FormsComponent = {
const data = await ApiService.lookupPostcode(this.form.postcode, this.form.houseno);
if (data.street) {
this.form.street = data.street.toUpperCase();
this.form.city = data.city.toUpperCase();
this.form.street = data.street;
this.form.city = data.city;
} else if (data.error) {
this.addressError = data.error;
} else {