Building a checkout for French customers, we hit the same wall twice: the address form. Not the API, not the payment provider — the form and the data behind it. Our stack was a boring Django app on a French VPS, Postgres 15, about 40k addresses in the table, roughly 8k signups a month. The failure was quiet: a handful of orders per week landed with a postal code in the city field, or a city in the street field, and one support ticket a day asking why the form rejected a perfectly valid address.
The first fix was to stop treating the postal code as a number. French postal codes are five characters, and the leading zero matters — 01000 is not 1000. Our validation was int() with a range check, which silently accepted 1000 and stored it as 1000. The column was integer, so we had to migrate:
ALTER TABLE addresses ALTER COLUMN postal_code TYPE varchar(5) USING lpad(postal_code::text, 5, '0');
ALTER TABLE addresses ADD CONSTRAINT postal_code_format CHECK (postal_code ~ '^[0-9]{5}$');
Second, we stopped asking people to type the city. We kept the free-text field but wired it to the official postal-code lookup, so entering 75011 fills in Paris. The input stays editable, because the official list is not always what people write — Paris 11e is a real thing humans type. That one change cut the daily support ticket to roughly one a week. The trade-off is a network call on blur; we cache the lookup table in Redis with a 24h TTL and fall back to free text if the call fails. Cost: about 4 MB of Redis, and an extra dependency on a third-party endpoint that we do not control. When it was down for an hour in March, signups still went through, just without the autofill.
Third, the address_line2 field. Half of France puts the building, floor, or chez line there, the other half leaves it empty. Our old form marked it required because the US form did. Removing the requirement was trivial; the harder part was the downstream label printer, which concatenated the fields and produced 12 rue de Rivoli , 75001 Paris with a floating comma. We now join non-empty parts in code and trim, which is a two-line function nobody will ever notice.
Where this breaks: the lookup does not help for cedex codes or for addresses in Monaco, which our French customers occasionally enter. And the lpad migration works only because we had no codes longer than five characters; if you have a postal_code column that already mixed in foreign codes, write the length check first and see what falls out before you alter the type.
For anyone who has run a French address form in production: how do you handle the rue/avenue/place prefix when matching against a carrier’s address validation API? We currently pass the raw string and accept whatever the carrier returns, and I suspect that is why a small share of labels still come back with the type stripped.
0 条评论
还没有评论,写下第一条。