From 0cbbff8a05b11a22db0e466f28a93494d3a3ba16 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 06:06:02 +0000 Subject: [PATCH 1/2] fix(order): reject WhatsApp numbers without 0, 62 or + prefix A bare number like 85335473895 passed validation as a generic 8-15 digit international number, but Fonnte never delivered to it. Without a leading 0, 62 or an explicit +, the number is now rejected on both the API and the client. The checkout prefill adds + back to stored non-Indonesian numbers so they stay valid on resubmit. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EkfojR2rGbgfbtPZufk3yD --- apps/api/domain/whatsapp_number.go | 6 ++++++ apps/api/domain/whatsapp_number_test.go | 3 +++ libs/ui/src/domain/entities/Customer.test.ts | 7 +++++-- libs/ui/src/domain/entities/Customer.ts | 7 ++++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/api/domain/whatsapp_number.go b/apps/api/domain/whatsapp_number.go index 7047cebf..ec310f32 100644 --- a/apps/api/domain/whatsapp_number.go +++ b/apps/api/domain/whatsapp_number.go @@ -10,11 +10,13 @@ func NormalizeWhatsappNumber(raw string) (string, *Error) { stripped := whatsappNumberSeparators.Replace(raw) var normalized string + hasExplicitCountryCode := false switch { case strings.HasPrefix(stripped, "0"): normalized = "62" + stripped[1:] case strings.HasPrefix(stripped, "+"): normalized = strings.TrimPrefix(stripped, "+") + hasExplicitCountryCode = true default: normalized = stripped } @@ -33,6 +35,10 @@ func NormalizeWhatsappNumber(raw string) (string, *Error) { return normalized, nil } + if !hasExplicitCountryCode { + return "", invalidWhatsappNumberError() + } + if len(normalized) < 8 || len(normalized) > 15 { return "", invalidWhatsappNumberError() } diff --git a/apps/api/domain/whatsapp_number_test.go b/apps/api/domain/whatsapp_number_test.go index 70a7fa12..78928555 100644 --- a/apps/api/domain/whatsapp_number_test.go +++ b/apps/api/domain/whatsapp_number_test.go @@ -33,6 +33,9 @@ func TestNormalizeWhatsappNumber(t *testing.T) { {name: "non-62 number at the 15 digit ceiling is accepted", raw: "+123456789012345", expected: "123456789012345"}, {name: "non-62 number over 15 digits is rejected", raw: "+1234567890123456", expectsErr: true}, {name: "bare plus sign is rejected", raw: "+", expectsErr: true}, + {name: "number without 0 or 62 prefix is rejected", raw: "85335473895", expectsErr: true}, + {name: "number without prefix and with separators is rejected", raw: "853-3547-3895", expectsErr: true}, + {name: "non-62 number without a plus is rejected", raw: "6591234567", expectsErr: true}, } for _, tt := range tests { diff --git a/libs/ui/src/domain/entities/Customer.test.ts b/libs/ui/src/domain/entities/Customer.test.ts index 01cf4a11..721b2469 100644 --- a/libs/ui/src/domain/entities/Customer.test.ts +++ b/libs/ui/src/domain/entities/Customer.test.ts @@ -28,6 +28,9 @@ describe('normalizeWhatsappNumber', () => { { name: 'non-62 number at the 15 digit ceiling is accepted', raw: '+123456789012345', expected: '123456789012345' }, { name: 'non-62 number over 15 digits is rejected', raw: '+1234567890123456', expected: null }, { name: 'bare plus sign is rejected', raw: '+', expected: null }, + { name: 'number without 0 or 62 prefix is rejected', raw: '85335473895', expected: null }, + { name: 'number without prefix and with separators is rejected', raw: '853-3547-3895', expected: null }, + { name: 'non-62 number without a plus is rejected', raw: '6591234567', expected: null }, ]; it.each(cases)('$name', ({ raw, expected }) => { @@ -40,8 +43,8 @@ describe('formatWhatsappNumberForInput', () => { expect(formatWhatsappNumberForInput('6281234567890')).toBe('081234567890'); }); - it('leaves a non-Indonesian number unchanged', () => { - expect(formatWhatsappNumberForInput('6591234567')).toBe('6591234567'); + it('shows a non-Indonesian number with a plus so it stays valid', () => { + expect(formatWhatsappNumberForInput('6591234567')).toBe('+6591234567'); }); it('leaves an empty string unchanged', () => { diff --git a/libs/ui/src/domain/entities/Customer.ts b/libs/ui/src/domain/entities/Customer.ts index 452c1acb..57f3c37f 100644 --- a/libs/ui/src/domain/entities/Customer.ts +++ b/libs/ui/src/domain/entities/Customer.ts @@ -9,10 +9,12 @@ export function normalizeWhatsappNumber(raw: string): string | null { const stripped = raw.replace(WHATSAPP_NUMBER_SEPARATORS, ''); let normalized: string; + let hasExplicitCountryCode = false; if (stripped.startsWith('0')) { normalized = '62' + stripped.slice(1); } else if (stripped.startsWith('+')) { normalized = stripped.slice(1); + hasExplicitCountryCode = true; } else { normalized = stripped; } @@ -25,12 +27,15 @@ export function normalizeWhatsappNumber(raw: string): string | null { return normalized; } + if (!hasExplicitCountryCode) return null; + if (normalized.length < 8 || normalized.length > 15) return null; return normalized; } export function formatWhatsappNumberForInput(normalized: string): string { + if (normalized === '') return normalized; if (normalized.startsWith('62')) return '0' + normalized.slice(2); - return normalized; + return '+' + normalized; } From 1918675998ca7504e02afb95ee4c48b76d6ab5b2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 02:15:28 +0000 Subject: [PATCH 2/2] test(order): cover 62-prefixed WhatsApp number without a plus Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EkfojR2rGbgfbtPZufk3yD --- apps/api/domain/whatsapp_number_test.go | 2 ++ libs/ui/src/domain/entities/Customer.test.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/api/domain/whatsapp_number_test.go b/apps/api/domain/whatsapp_number_test.go index 78928555..44d1d21b 100644 --- a/apps/api/domain/whatsapp_number_test.go +++ b/apps/api/domain/whatsapp_number_test.go @@ -16,6 +16,8 @@ func TestNormalizeWhatsappNumber(t *testing.T) { }{ {name: "leading 0 becomes 62", raw: "0812-3456-7890", expected: "6281234567890"}, {name: "628 prefix is unchanged", raw: "6281234567890", expected: "6281234567890"}, + {name: "62 prefix without a plus is accepted", raw: "6285335473895", expected: "6285335473895"}, + {name: "0 prefix converts to the same number", raw: "085335473895", expected: "6285335473895"}, {name: "+628 drops the plus", raw: "+62 812 3456 7890", expected: "6281234567890"}, {name: "other country plus is dropped, kept if 8-15 digits", raw: "+6591234567", expected: "6591234567"}, {name: "dots and parentheses are stripped", raw: "(0812).3456.7890", expected: "6281234567890"}, diff --git a/libs/ui/src/domain/entities/Customer.test.ts b/libs/ui/src/domain/entities/Customer.test.ts index 721b2469..d04156c7 100644 --- a/libs/ui/src/domain/entities/Customer.test.ts +++ b/libs/ui/src/domain/entities/Customer.test.ts @@ -11,6 +11,8 @@ describe('normalizeWhatsappNumber', () => { }[] = [ { name: 'leading 0 becomes 62', raw: '0812-3456-7890', expected: '6281234567890' }, { name: '628 prefix is unchanged', raw: '6281234567890', expected: '6281234567890' }, + { name: '62 prefix without a plus is accepted', raw: '6285335473895', expected: '6285335473895' }, + { name: '0 prefix converts to the same number', raw: '085335473895', expected: '6285335473895' }, { name: '+628 drops the plus', raw: '+62 812 3456 7890', expected: '6281234567890' }, { name: 'other country plus is dropped, kept if 8-15 digits', raw: '+6591234567', expected: '6591234567' }, { name: 'dots and parentheses are stripped', raw: '(0812).3456.7890', expected: '6281234567890' },