diff --git a/cmd/chat.go b/cmd/chat.go index ec24fbc2..a048d3ae 100644 --- a/cmd/chat.go +++ b/cmd/chat.go @@ -64,6 +64,7 @@ type Claims struct { FirstName string `json:"first_name,omitempty"` LastName string `json:"last_name,omitempty"` PhoneNumber string `json:"phone_number,omitempty"` + PhoneNumberCountryCode string `json:"phone_number_country_code,omitempty"` ContactCustomAttributes map[string]any `json:"contact_custom_attributes,omitempty"` jwt.RegisteredClaims } @@ -388,6 +389,10 @@ func handleAuthExchange(r *fastglue.Request) error { if len(claims.PhoneNumber) > maxPhoneNumberLength { return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.maxLength", "max", strconv.Itoa(maxPhoneNumberLength)), nil, envelope.InputError) } + // Country code is cosmetic - drop an invalid one instead of failing the whole exchange. + if len(claims.PhoneNumberCountryCode) > maxPhoneCountryCodeLength { + claims.PhoneNumberCountryCode = "" + } // Resolve or create the contact. contactID, err := resolveOrCreateExternalContact(app, claims) @@ -774,8 +779,8 @@ func resolveOrCreateExternalContact(app *App, claims Claims) (int, error) { // Sync name/email/phone from JWT only if changed. if user.ID > 0 && claims.ExternalUserID != "" { if user.FirstName != claims.FirstName || user.LastName != claims.LastName || user.Email.String != claims.Email || - user.PhoneNumber.String != claims.PhoneNumber { - if err := app.user.UpdateContactBasicInfo(user.ID, claims.FirstName, claims.LastName, claims.Email, claims.PhoneNumber); err != nil { + user.PhoneNumber.String != claims.PhoneNumber || user.PhoneNumberCountryCode.String != claims.PhoneNumberCountryCode { + if err := app.user.UpdateContactBasicInfo(user.ID, claims.FirstName, claims.LastName, claims.Email, claims.PhoneNumber, claims.PhoneNumberCountryCode); err != nil { app.lo.Error("error updating contact basic info", "contact_id", user.ID, "error", err) } } @@ -785,12 +790,13 @@ func resolveOrCreateExternalContact(app *App, claims Claims) (int, error) { // Create contact if not found. if claims.ExternalUserID != "" { user := umodels.User{ - FirstName: claims.FirstName, - LastName: claims.LastName, - Email: null.NewString(claims.Email, true), - PhoneNumber: null.NewString(claims.PhoneNumber, claims.PhoneNumber != ""), - ExternalUserID: null.NewString(claims.ExternalUserID, true), - CustomAttributes: marshalCustomAttributes(claims.ContactCustomAttributes, app), + FirstName: claims.FirstName, + LastName: claims.LastName, + Email: null.NewString(claims.Email, true), + PhoneNumber: null.NewString(claims.PhoneNumber, claims.PhoneNumber != ""), + PhoneNumberCountryCode: null.NewString(claims.PhoneNumberCountryCode, claims.PhoneNumberCountryCode != ""), + ExternalUserID: null.NewString(claims.ExternalUserID, true), + CustomAttributes: marshalCustomAttributes(claims.ContactCustomAttributes, app), } if err := app.user.CreateContact(&user); err != nil { return 0, err diff --git a/frontend/apps/main/src/features/admin/inbox/LivechatInboxForm.vue b/frontend/apps/main/src/features/admin/inbox/LivechatInboxForm.vue index 01da5c53..d7cb9919 100644 --- a/frontend/apps/main/src/features/admin/inbox/LivechatInboxForm.vue +++ b/frontend/apps/main/src/features/admin/inbox/LivechatInboxForm.vue @@ -1119,7 +1119,8 @@ const jwtPayloadExample = computed(() => { "email": "user@example.com", // Required: User's email "first_name": "John", // Required: User's first name "last_name": "Doe", // Optional: User's last name - "phone_number": "+15551234567", // Optional: User's phone number + "phone_number": "9876543210", // Optional: User's phone number (no country calling code) + "phone_number_country_code": "IN", // Optional: ISO 3166-1 alpha-2 country code (e.g. "IN", "US") "exp": 1735689600, // Required: Expiration time (Unix timestamp in seconds) "contact_custom_attributes": { // Optional: Contact-level attributes "plan": "premium", diff --git a/internal/user/contact.go b/internal/user/contact.go index 02bc3bbe..c15b1901 100644 --- a/internal/user/contact.go +++ b/internal/user/contact.go @@ -75,8 +75,8 @@ func (u *Manager) CreateContact(user *models.User) error { } // UpdateContactBasicInfo updates only the name, email and phone of a contact. -func (u *Manager) UpdateContactBasicInfo(id int, firstName, lastName, email, phoneNumber string) error { - if _, err := u.q.UpdateContactBasicInfo.Exec(id, firstName, lastName, strings.ToLower(strings.TrimSpace(email)), phoneNumber); err != nil { +func (u *Manager) UpdateContactBasicInfo(id int, firstName, lastName, email, phoneNumber, phoneNumberCountryCode string) error { + if _, err := u.q.UpdateContactBasicInfo.Exec(id, firstName, lastName, strings.ToLower(strings.TrimSpace(email)), phoneNumber, phoneNumberCountryCode); err != nil { u.lo.Error("error updating contact basic info", "error", err) return fmt.Errorf("updating contact basic info: %w", err) } diff --git a/internal/user/queries.sql b/internal/user/queries.sql index 53f0ac88..a64a2dae 100644 --- a/internal/user/queries.sql +++ b/internal/user/queries.sql @@ -240,6 +240,7 @@ SET first_name = COALESCE(NULLIF($2, ''), first_name), last_name = COALESCE(NULLIF($3, ''), last_name), email = COALESCE(NULLIF($4, ''), email), phone_number = COALESCE(NULLIF($5, ''), phone_number), + phone_number_country_code = COALESCE(NULLIF($6, ''), phone_number_country_code), updated_at = now() WHERE id = $1 AND type IN ('contact', 'visitor');