feat(api): add phone number registration endpoint and update related logic
Some checks failed
CI / Test (1.23) (push) Failing after -21m47s
CI / Test (1.22) (push) Failing after -21m38s
CI / Lint (push) Failing after -21m58s
CI / Build (push) Failing after -22m23s

This commit is contained in:
2026-02-21 00:04:16 +02:00
parent 8732b332a1
commit 4a716bb82d
10 changed files with 275 additions and 43 deletions

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
@@ -20,18 +21,20 @@ import (
// Client represents a WhatsApp Business API client
type Client struct {
id string
phoneNumber string
config config.BusinessAPIConfig
httpClient *http.Client
eventBus *events.EventBus
mediaConfig config.MediaConfig
connected bool
mu sync.RWMutex
id string
phoneNumber string
config config.BusinessAPIConfig
httpClient *http.Client
eventBus *events.EventBus
mediaConfig config.MediaConfig
connected bool
wabaID string // WhatsApp Business Account ID, resolved at connect time
onWABAResolved func(accountID, wabaID string) // called when WABA ID is resolved for the first time
mu sync.RWMutex
}
// NewClient creates a new Business API client
func NewClient(cfg config.WhatsAppConfig, eventBus *events.EventBus, mediaConfig config.MediaConfig) (*Client, error) {
func NewClient(cfg config.WhatsAppConfig, eventBus *events.EventBus, mediaConfig config.MediaConfig, onWABAResolved func(accountID, wabaID string)) (*Client, error) {
if cfg.Type != "business-api" {
return nil, fmt.Errorf("invalid client type for business-api: %s", cfg.Type)
}
@@ -60,9 +63,10 @@ func NewClient(cfg config.WhatsAppConfig, eventBus *events.EventBus, mediaConfig
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
eventBus: eventBus,
mediaConfig: mediaConfig,
connected: false,
eventBus: eventBus,
mediaConfig: mediaConfig,
connected: false,
onWABAResolved: onWABAResolved,
}, nil
}
@@ -123,7 +127,24 @@ func (c *Client) Connect(ctx context.Context) error {
"status", phoneDetails.CodeVerificationStatus)
}
// Step 3: Get business account details (if business_account_id is provided)
// Step 3: Resolve the WhatsApp Business Account (WABA) ID from the phone number.
// The WABA ID is required for phone number listing; it differs from the Facebook Business Manager ID.
// Use cached value from config if already known.
if c.config.WABAId != "" {
c.wabaID = c.config.WABAId
logging.Info("WhatsApp Business Account ID loaded from config", "account_id", c.id, "waba_id", c.wabaID)
} else if wabaID, err := c.fetchWABAID(ctx); err != nil {
logging.Warn("Failed to resolve WABA ID (non-critical)", "account_id", c.id, "error", err)
} else {
c.wabaID = wabaID
c.config.WABAId = wabaID
logging.Info("WhatsApp Business Account ID resolved", "account_id", c.id, "waba_id", wabaID)
if c.onWABAResolved != nil {
c.onWABAResolved(c.id, wabaID)
}
}
// Step 4: Get business account details (if business_account_id is provided)
if c.config.BusinessAccountID != "" {
businessDetails, err := c.getBusinessAccountDetails(ctx)
if err != nil {
@@ -532,6 +553,42 @@ func (c *Client) checkMissingScopes(currentScopes []string, requiredScopes []str
return missing
}
// fetchWABAID resolves the WhatsApp Business Account (WABA) ID by iterating the WABAs
// owned by the configured business account and matching against our phone number ID.
// Requires business_account_id to be set in config.
// GET /{business-id}/owned_whatsapp_business_accounts?fields=id,phone_numbers{id}
func (c *Client) fetchWABAID(ctx context.Context) (string, error) {
if c.config.BusinessAccountID == "" {
return "", fmt.Errorf("waba_id or business_account_id must be set in config to resolve WABA")
}
var result struct {
Data []struct {
ID string `json:"id"`
PhoneNumbers struct {
Data []struct {
ID string `json:"id"`
} `json:"data"`
} `json:"phone_numbers"`
} `json:"data"`
}
params := url.Values{"fields": {"id,phone_numbers{id}"}}
if err := c.graphAPIGet(ctx, c.config.BusinessAccountID+"/owned_whatsapp_business_accounts", params, &result); err != nil {
return "", fmt.Errorf("failed to list WABAs for business %s: %w", c.config.BusinessAccountID, err)
}
for _, waba := range result.Data {
for _, phone := range waba.PhoneNumbers.Data {
if phone.ID == c.config.PhoneNumberID {
return waba.ID, nil
}
}
}
return "", fmt.Errorf("phone number %s not found in any WABA owned by business %s", c.config.PhoneNumberID, c.config.BusinessAccountID)
}
// formatExpiry formats the expiry timestamp for logging
func (c *Client) formatExpiry(expiresAt int64) string {
if expiresAt == 0 {