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

3.0 KiB

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

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

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:

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/