cast-ghl-plugin/.claude/tasks/04-phone-normalize.md
Head of Product & Engineering a40a4aa626
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat: initial implementation of Cast GHL Provider
Complete MVP implementation of the Cast GHL Conversation Provider bridge:
- Go module setup with chi router and mongo-driver dependencies
- Config loading with env var validation and defaults
- MongoDB token store with upsert, get, update, delete operations
- Cast.ph SMS client with 429 retry logic and typed errors
- Phone number normalization (E.164 ↔ Philippine local format)
- GHL OAuth 2.0 install/callback/refresh flow
- GHL webhook handler with ECDSA signature verification (async dispatch)
- GHL API client for message status updates and inbound message stubs
- Multi-stage Dockerfile, docker-compose with MongoDB, Woodpecker CI pipeline
- Unit tests for phone normalization, Cast client, GHL webhook, and OAuth handlers

Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2026-04-04 17:27:05 +02:00

91 lines
3.0 KiB
Markdown

# Task 04: Phone Number Normalization
## Objective
Build `internal/phone/normalize.go` — bidirectional conversion between E.164 and Philippine local format.
## Functions
### ToLocal — E.164 → Philippine local
```go
func ToLocal(e164 string) (string, error)
```
| Input | Output | Notes |
|-------|--------|-------|
| `+639171234567` | `09171234567` | Standard E.164 |
| `639171234567` | `09171234567` | Missing `+` prefix |
| `09171234567` | `09171234567` | Already local, pass through |
| `9171234567` | `09171234567` | Missing leading `0` |
| `+1234567890` | error | Non-PH country code |
| `` | error | Empty |
| `abc` | error | Non-numeric |
Rules:
1. Strip all non-digit characters (spaces, dashes, parens, `+`)
2. If starts with `63` and length is 12: replace `63` with `0`
3. If starts with `9` and length is 10: prepend `0`
4. If starts with `0` and length is 11: pass through
5. Otherwise: return error "invalid Philippine phone number"
6. Validate result is exactly 11 digits starting with `09`
### ToE164 — Philippine local → E.164
```go
func ToE164(local string) (string, error)
```
| Input | Output | Notes |
|-------|--------|-------|
| `09171234567` | `+639171234567` | Standard local |
| `9171234567` | `+639171234567` | Missing leading `0` |
| `+639171234567` | `+639171234567` | Already E.164, pass through |
| `` | error | Empty |
Rules:
1. Strip all non-digit characters except leading `+`
2. If starts with `+63`: pass through
3. If starts with `63` and length is 12: prepend `+`
4. If starts with `0` and length is 11: replace `0` with `+63`
5. If starts with `9` and length is 10: prepend `+63`
6. Otherwise: return error "invalid Philippine phone number"
7. Validate result matches `+63` + 10 digits
## Tests (`internal/phone/normalize_test.go`)
Test both functions with the table-driven test pattern:
```go
func TestToLocal(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{"e164 with plus", "+639171234567", "09171234567", false},
{"e164 without plus", "639171234567", "09171234567", false},
{"already local", "09171234567", "09171234567", false},
{"missing leading zero", "9171234567", "09171234567", false},
{"non-PH number", "+1234567890", "", true},
{"empty", "", "", true},
{"with spaces", "+63 917 123 4567", "09171234567", false},
{"with dashes", "0917-123-4567", "09171234567", false},
{"too short", "0917", "", true},
{"too long", "091712345678", "", true},
}
// ... run tests
}
```
Same pattern for `TestToE164`.
## Acceptance Criteria
- [ ] `go build ./cmd/server/` succeeds
- [ ] `ToLocal` handles all input formats in the table
- [ ] `ToE164` handles all input formats in the table
- [ ] Non-Philippine numbers return error
- [ ] Empty strings return error
- [ ] Non-numeric input returns error
- [ ] Spaces, dashes, parens are stripped
- [ ] Results are validated (length + prefix check)
- [ ] All tests pass: `go test ./internal/phone/`