mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2025-02-12 02:59:24 +03:00
225 lines
6.4 KiB
Go
225 lines
6.4 KiB
Go
package util
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"regexp"
|
||
"slices"
|
||
"strconv"
|
||
"strings"
|
||
|
||
phoneiso3166 "github.com/onlinecity/go-phone-iso3166"
|
||
pn "github.com/ttacon/libphonenumber"
|
||
)
|
||
|
||
const (
|
||
MinPhoneSymbolCount = 5
|
||
CountryPhoneCodeDE = 49
|
||
CountryPhoneCodeAG = 54
|
||
CountryPhoneCodeMX = 52
|
||
// CountryPhoneCodeMXWA For Whatsapp.
|
||
CountryPhoneCodeMXWA = 521
|
||
CountryPhoneCodeUS = "1443"
|
||
CountryPhoneCodePS = 970
|
||
CountryPhoneCodeUZ = 998
|
||
PalestineRegion = "PS"
|
||
BangladeshRegion = "BD"
|
||
)
|
||
|
||
var (
|
||
ErrPhoneTooShort = errors.New("phone is too short - must be at least 5 symbols")
|
||
ErrCannotDetermineCountry = errors.New("cannot determine phone country code")
|
||
ErrCannotParsePhone = errors.New("cannot parse phone number")
|
||
undefinedUSCodes = []string{"1445", "1945", "1840", "1448", "1279", "1839"}
|
||
)
|
||
|
||
// FormatNumberForWA forms a number in E164 format without `+` symbol to send to whatsapp.
|
||
func FormatNumberForWA(number string) (string, error) {
|
||
parsedPhone, err := ParsePhone(number)
|
||
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
var formattedPhoneNumber string
|
||
switch parsedPhone.GetCountryCode() {
|
||
case CountryPhoneCodeAG:
|
||
formattedPhoneNumber = Add9AGIFNeed(parsedPhone)
|
||
case CountryPhoneCodeMX:
|
||
c := int32(CountryPhoneCodeMXWA)
|
||
parsedPhone.CountryCode = &c
|
||
fallthrough
|
||
default:
|
||
formattedPhoneNumber = pn.Format(parsedPhone, pn.E164)
|
||
}
|
||
|
||
return formattedPhoneNumber[1:], nil
|
||
}
|
||
|
||
// FormatNumberForMG forms a number in E164 format without `+` symbol to send to Message Gateway
|
||
// TODO Возможно, нет смысла в этих функция, так как в КР и 360 будет своя логика.
|
||
func FormatNumberForMG(number string) (string, error) {
|
||
parsedPhone, err := ParsePhone(number)
|
||
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
var formattedPhoneNumber string
|
||
switch parsedPhone.GetCountryCode() {
|
||
case CountryPhoneCodeAG:
|
||
formattedPhoneNumber = Remove9AGIfNeed(parsedPhone)
|
||
case CountryPhoneCodeMX:
|
||
c := int32(CountryPhoneCodeMXWA)
|
||
parsedPhone.CountryCode = &c
|
||
fallthrough
|
||
default:
|
||
formattedPhoneNumber = pn.Format(parsedPhone, pn.E164)
|
||
}
|
||
|
||
return formattedPhoneNumber[1:], nil
|
||
}
|
||
|
||
// ParsePhone this function parses the number as a string
|
||
// Mexican numbers may not have a 1 after the country code 52.
|
||
func ParsePhone(phoneNumber string) (*pn.PhoneNumber, error) {
|
||
trimmedPhone := regexp.MustCompile(`\D+`).ReplaceAllString(phoneNumber, "")
|
||
if len(trimmedPhone) < MinPhoneSymbolCount {
|
||
return nil, ErrPhoneTooShort
|
||
}
|
||
|
||
countryCode := getCountryCode(trimmedPhone)
|
||
if countryCode == "" {
|
||
return nil, ErrCannotDetermineCountry
|
||
}
|
||
|
||
// For russian numbers as 8800xxxxxxx
|
||
if strings.EqualFold(BangladeshRegion, countryCode) && IsRussianNumberWith8Prefix(trimmedPhone) {
|
||
countryCode = phoneiso3166.E164.LookupString("7" + trimmedPhone[1:])
|
||
}
|
||
|
||
parsedPhone, err := pn.Parse(trimmedPhone, countryCode)
|
||
|
||
if err != nil {
|
||
return nil, ErrCannotParsePhone
|
||
}
|
||
|
||
if CountryPhoneCodeDE == parsedPhone.GetCountryCode() {
|
||
number, err := getGermanNationalNumber(trimmedPhone, parsedPhone)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
parsedPhone.NationalNumber = &number
|
||
}
|
||
|
||
if CountryPhoneCodeUZ == parsedPhone.GetCountryCode() {
|
||
number, err := getUzbekistanNationalNumber(trimmedPhone, parsedPhone)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
parsedPhone.NationalNumber = &number
|
||
}
|
||
|
||
return parsedPhone, err
|
||
}
|
||
|
||
func IsRussianNumberWith8Prefix(phone string) bool {
|
||
return strings.HasPrefix(phone, "8") && len(phone) == 11 && phoneiso3166.E164.LookupString("7"+phone[1:]) == "RU"
|
||
}
|
||
|
||
func IsMexicoNumber(phone string, parsed *pn.PhoneNumber) bool {
|
||
phoneNumber := regexp.MustCompile(`\D+`).ReplaceAllString(phone, "")
|
||
return len(phoneNumber) == 13 && parsed.GetCountryCode() == 52 && strings.HasPrefix(phoneNumber, "521")
|
||
}
|
||
|
||
func IsUSNumber(phone string) bool {
|
||
return slices.Contains(undefinedUSCodes, phone[:4]) &&
|
||
phoneiso3166.E164.LookupString(CountryPhoneCodeUS+phone[4:]) == "US"
|
||
}
|
||
|
||
func IsPLNumber(phone string) bool {
|
||
num, err := pn.Parse(phone, "PS")
|
||
return err == nil && num.GetCountryCode() == CountryPhoneCodePS && fmt.Sprintf("%d", CountryPhoneCodePS) == phone[0:3]
|
||
}
|
||
|
||
func Remove9AGIfNeed(parsedPhone *pn.PhoneNumber) string {
|
||
formattedPhone := pn.Format(parsedPhone, pn.E164)
|
||
numberWOCountry := fmt.Sprintf("%d", parsedPhone.GetNationalNumber())
|
||
|
||
if len(numberWOCountry) == 11 && string(numberWOCountry[0]) == "9" {
|
||
formattedPhone = fmt.Sprintf("+%d%s", CountryPhoneCodeAG, numberWOCountry[1:])
|
||
}
|
||
|
||
return formattedPhone
|
||
}
|
||
|
||
func Add9AGIFNeed(parsedPhone *pn.PhoneNumber) string {
|
||
formattedPhone := pn.Format(parsedPhone, pn.E164)
|
||
numberWOCountry := fmt.Sprintf("%d", parsedPhone.GetNationalNumber())
|
||
|
||
if len(numberWOCountry) == 10 { // nolint:mnd
|
||
formattedPhone = fmt.Sprintf("+%d%s", CountryPhoneCodeAG, "9"+numberWOCountry)
|
||
}
|
||
|
||
return formattedPhone
|
||
}
|
||
|
||
// getGermanNationalNumber some German numbers may not be parsed correctly.
|
||
// For example, for 491736276098 libphonenumber.PhoneNumber.NationalNumber
|
||
// will contain the country code(49). This function fix it and return correct libphonenumber.PhoneNumber.
|
||
func getGermanNationalNumber(phone string, parsedPhone *pn.PhoneNumber) (uint64, error) {
|
||
result := parsedPhone.GetNationalNumber()
|
||
|
||
if len(fmt.Sprintf("%d", parsedPhone.GetNationalNumber())) == len(phone) {
|
||
deduplicateCountryNumber := fmt.Sprintf("%d", parsedPhone.GetNationalNumber())[2:]
|
||
|
||
number, err := strconv.Atoi(deduplicateCountryNumber)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
result = uint64(number) //nolint:gosec
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// For UZ numbers where 8 is deleted after the country code.
|
||
func getUzbekistanNationalNumber(phone string, parsedPhone *pn.PhoneNumber) (uint64, error) {
|
||
result := parsedPhone.GetNationalNumber()
|
||
numberWithEight := fmt.Sprintf("8%d", parsedPhone.GetNationalNumber())
|
||
|
||
if len(fmt.Sprintf("%d%s", parsedPhone.GetCountryCode(), numberWithEight)) == len(phone) {
|
||
number, err := strconv.Atoi(numberWithEight)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
result = uint64(number) //nolint:gosec
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
func getCountryCode(phone string) string {
|
||
countryCode := phoneiso3166.E164.LookupString(phone)
|
||
|
||
if countryCode == "" {
|
||
if IsRussianNumberWith8Prefix(phone) {
|
||
countryCode = phoneiso3166.E164.LookupString("7" + phone[1:])
|
||
}
|
||
|
||
if IsUSNumber(phone) {
|
||
countryCode = phoneiso3166.E164.LookupString(CountryPhoneCodeUS + phone[4:])
|
||
}
|
||
|
||
if IsPLNumber(phone) {
|
||
countryCode = PalestineRegion
|
||
}
|
||
}
|
||
|
||
return countryCode
|
||
}
|