add zambia phone numbers
This commit is contained in:
@@ -54,7 +54,8 @@ import getReceiveAddressAndContactForContactsPayment from '../contacts/internalC
|
||||
import { hasStringAsync } from 'expo-clipboard';
|
||||
import { scheduleOnRN } from 'react-native-worklets';
|
||||
import { KeyboardController } from 'react-native-keyboard-controller';
|
||||
import { parsePhoneNumberWithError } from 'libphonenumber-js';
|
||||
import { isPhonePaymentNumber } from '../../../../functions/sendBitcoin/getPhonePaymentAddress';
|
||||
import IconActionCircle from '../../../../functions/CustomElements/actionCircleContainer';
|
||||
|
||||
const ContactRow = ({
|
||||
contact,
|
||||
@@ -361,18 +362,10 @@ export default function HalfModalSendOptions({
|
||||
return inputText.startsWith('@') ? inputText.slice(1).trim() : '';
|
||||
}, [inputText]);
|
||||
|
||||
const kenyanPhoneNumber = useMemo(() => {
|
||||
const stripped = inputText.trim();
|
||||
if (!stripped) return null;
|
||||
try {
|
||||
const normalized = stripped.startsWith('+') ? stripped : `+${stripped}`;
|
||||
const parsed = parsePhoneNumberWithError(normalized);
|
||||
if (parsed.country === 'KE' && parsed.isValid()) {
|
||||
return parsed.number.slice(1);
|
||||
}
|
||||
} catch {}
|
||||
return null;
|
||||
}, [inputText]);
|
||||
const isPhoneNumber = useMemo(
|
||||
() => isPhonePaymentNumber(inputText),
|
||||
[inputText],
|
||||
);
|
||||
|
||||
const contentOpacity = useSharedValue(1);
|
||||
const contentTranslateX = useSharedValue(0);
|
||||
@@ -443,21 +436,6 @@ export default function HalfModalSendOptions({
|
||||
if (!inputText.trim()) return;
|
||||
const input = inputText.trim();
|
||||
|
||||
try {
|
||||
const phoneNormalized = input.startsWith('+') ? input : `+${input}`;
|
||||
const parsed = parsePhoneNumberWithError(phoneNormalized);
|
||||
if (parsed.country === 'KE' && parsed.isValid()) {
|
||||
const lightningAddress = `${parsed.number.slice(1)}@bitcoin.co.ke`;
|
||||
handleBackPressFunction(async () => {
|
||||
navigate.replace('ConfirmPaymentScreen', {
|
||||
btcAdress: lightningAddress,
|
||||
fromPage: '',
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const normalized = input.startsWith('@')
|
||||
? input.slice(1).toLowerCase()
|
||||
: input.toLowerCase();
|
||||
@@ -1153,19 +1131,31 @@ export default function HalfModalSendOptions({
|
||||
/>
|
||||
</View>
|
||||
) : null
|
||||
) : kenyanPhoneNumber ? (
|
||||
) : isPhoneNumber ? (
|
||||
<View style={styles.noContactContainer}>
|
||||
<ThemeIcon iconName={'Phone'} />
|
||||
<ThemeText
|
||||
styles={styles.emptyTitle}
|
||||
content={t('wallet.halfModal.kenyanPhoneTitle', {
|
||||
number: inputText.trim(),
|
||||
})}
|
||||
<IconActionCircle
|
||||
size={70}
|
||||
icon={'Phone'}
|
||||
customBackgroundColor={
|
||||
theme && darkModeType ? backgroundColor : undefined
|
||||
}
|
||||
bottomOffset={10}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.emptySubtext}
|
||||
content={t('wallet.halfModal.kenyanPhoneDesc')}
|
||||
styles={[
|
||||
styles.emptySubtext,
|
||||
{ fontSize: SIZES.smedium, marginBottom: 10 },
|
||||
]}
|
||||
content={t('wallet.halfModal.phonePaymentDesc')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={[
|
||||
styles.emptyTitle,
|
||||
{ marginTop: 0, fontSize: SIZES.xLarge },
|
||||
]}
|
||||
content={inputText}
|
||||
/>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={{ ...CENTER, marginTop: 'auto' }}
|
||||
textContent={t('constants.pay')}
|
||||
|
||||
@@ -23,6 +23,9 @@ import { receiveSparkLightningPayment } from '../../../../../functions/spark';
|
||||
import { isBlitzLNURLAddress } from '../../../../../functions/lnurl';
|
||||
import { handleBrantaVerification } from '../../../../../functions/branta/index';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import getPhonePaymentAddress, {
|
||||
getPhonePaymentCandidates,
|
||||
} from '../../../../../functions/sendBitcoin/getPhonePaymentAddress';
|
||||
|
||||
export default async function decodeSendAddress(props) {
|
||||
let {
|
||||
@@ -115,10 +118,19 @@ export default async function decodeSendAddress(props) {
|
||||
};
|
||||
}
|
||||
|
||||
// Phone-number payments (KE/ZM): convert the dialed number into a provider
|
||||
// lightning address. Use the preferred candidate (KE first) optimistically so
|
||||
// parseInput's own LNURL fetch validates it; probe further only if it fails.
|
||||
const phoneInput = btcAdress;
|
||||
const phoneCandidates = getPhonePaymentCandidates(phoneInput);
|
||||
const isPhonePayment = phoneCandidates.length > 0;
|
||||
if (isPhonePayment) btcAdress = phoneCandidates[0];
|
||||
|
||||
if (
|
||||
btcAdress.startsWith('@') ||
|
||||
btcAdress.length <= 30 ||
|
||||
isBlitzLNURLAddress(btcAdress)
|
||||
!isPhonePayment &&
|
||||
(btcAdress.startsWith('@') ||
|
||||
btcAdress.length <= 30 ||
|
||||
isBlitzLNURLAddress(btcAdress))
|
||||
) {
|
||||
let username = '';
|
||||
|
||||
@@ -234,10 +246,22 @@ export default async function decodeSendAddress(props) {
|
||||
input = await chosenPath;
|
||||
if (!input) throw new Error('Invalid address provided');
|
||||
} catch (err) {
|
||||
console.log(err, 'parse error');
|
||||
return goBackFunction(
|
||||
t('wallet.sendPages.handlingAddressErrors.parseError'),
|
||||
);
|
||||
if (isPhonePayment && phoneCandidates.length > 1) {
|
||||
const resolved = await getPhonePaymentAddress(phoneInput);
|
||||
btcAdress = resolved;
|
||||
try {
|
||||
input = await parseInput(resolved);
|
||||
} catch (err) {
|
||||
return goBackFunction(
|
||||
t('wallet.sendPages.handlingAddressErrors.parseError'),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(err, 'parse error');
|
||||
return goBackFunction(
|
||||
t('wallet.sendPages.handlingAddressErrors.parseError'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let processedPaymentInfo;
|
||||
|
||||
@@ -8,6 +8,7 @@ export default function IconActionCircle({
|
||||
size = 80,
|
||||
icon,
|
||||
bottomOffset = 0,
|
||||
customBackgroundColor,
|
||||
}) {
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { backgroundColor, backgroundOffset } = GetThemeColors();
|
||||
@@ -16,7 +17,7 @@ export default function IconActionCircle({
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{
|
||||
backgroundColor: backgroundOffset,
|
||||
backgroundColor: customBackgroundColor ?? backgroundOffset,
|
||||
borderColor: backgroundColor,
|
||||
},
|
||||
{
|
||||
@@ -32,6 +33,7 @@ export default function IconActionCircle({
|
||||
theme && darkModeType ? COLORS.darkModeText : COLORS.primary
|
||||
}
|
||||
iconName={icon}
|
||||
size={Math.round(size * 0.375)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { parsePhoneNumberWithError } from 'libphonenumber-js';
|
||||
import getLNURLDetails from '../lnurl/getLNURLDetails';
|
||||
|
||||
// country -> bitcoin payment provider; formatNumber emits the provider's
|
||||
// canonical format regardless of whether input was national or international.
|
||||
const PHONE_PAYMENT_PROVIDERS = {
|
||||
KE: {
|
||||
domain: 'bitcoin.co.ke',
|
||||
formatNumber: parsed => parsed.number.slice(1),
|
||||
}, // 254...
|
||||
ZM: {
|
||||
domain: 'bitzed.xyz',
|
||||
formatNumber: parsed => `0${parsed.nationalNumber}`,
|
||||
}, // 0977...
|
||||
};
|
||||
|
||||
// Returns the provider lightning addresses the input is valid for, in
|
||||
// PHONE_PAYMENT_PROVIDERS order (KE before ZM). Accepts national or
|
||||
// international input. A bare national number in the overlapping 075/076/077
|
||||
// range is valid for both KE and ZM, so this can return more than one.
|
||||
export function getPhonePaymentCandidates(input) {
|
||||
const stripped = (input || '').trim();
|
||||
if (!stripped) return [];
|
||||
|
||||
// Try international form first, then national form per supported country.
|
||||
const normalized = stripped.startsWith('+') ? stripped : `+${stripped}`;
|
||||
const attempts = [
|
||||
[normalized, undefined],
|
||||
...Object.keys(PHONE_PAYMENT_PROVIDERS).map(country => [stripped, country]),
|
||||
];
|
||||
|
||||
const seen = new Set();
|
||||
const candidates = [];
|
||||
for (const [value, defaultCountry] of attempts) {
|
||||
try {
|
||||
const parsed = parsePhoneNumberWithError(value, defaultCountry);
|
||||
const provider = PHONE_PAYMENT_PROVIDERS[parsed.country];
|
||||
if (provider && parsed.isValid() && !seen.has(parsed.country)) {
|
||||
seen.add(parsed.country);
|
||||
candidates.push(`${provider.formatNumber(parsed)}@${provider.domain}`);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
// Sync gate for the preview UI: is this input a payable phone number?
|
||||
export function isPhonePaymentNumber(input) {
|
||||
return getPhonePaymentCandidates(input).length > 0;
|
||||
}
|
||||
|
||||
// Resolves the input to a single lightning address. When the number is valid
|
||||
// for multiple supported countries (overlapping 075/076/077 range), probe each
|
||||
// candidate's LNURL endpoint in order (KE first) and use the first one that
|
||||
// resolves to a valid pay request; otherwise fall back to the last candidate.
|
||||
export default async function getPhonePaymentAddress(input) {
|
||||
const candidates = getPhonePaymentCandidates(input);
|
||||
if (candidates.length === 0) return null;
|
||||
if (candidates.length === 1) return candidates[0];
|
||||
|
||||
for (let i = 0; i < candidates.length - 1; i++) {
|
||||
const details = await getLNURLDetails(candidates[i]);
|
||||
if (details && details.tag === 'payRequest') return candidates[i];
|
||||
}
|
||||
return candidates[candidates.length - 1];
|
||||
}
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Sammeln Sie Beiträge von einer Gruppe für ein gemeinsames Ziel.",
|
||||
"noContactHead": "{{username}} nicht gefunden",
|
||||
"noContactDesc": "Fügen Sie {{username}} zu Ihren Kontakten hinzu, damit der Kontakt hier künftig angezeigt wird, oder fahren Sie fort, um jetzt eine Zahlung zu senden.",
|
||||
"kenyanPhoneTitle": "{{number}} bezahlen",
|
||||
"kenyanPhoneDesc": "Sie sind dabei, eine kenianische Telefonnummer zu bezahlen",
|
||||
"phonePaymentTitle": "{{number}} bezahlen",
|
||||
"phonePaymentDesc": "Senden an Mobilnummer",
|
||||
"depositFunds": "Guthaben einzahlen",
|
||||
"depositFundsSubtitle": "Fügen Sie Ihrem Wallet Guthaben hinzu",
|
||||
"onChainBitcoin": "Bitcoin-Adresse",
|
||||
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Collect contributions from a group toward a shared goal.",
|
||||
"noContactHead": "{{username}} not found",
|
||||
"noContactDesc": "Add {{username}} to your contacts to find them here in the future, or continue to send a payment now.",
|
||||
"kenyanPhoneTitle": "Pay {{number}}",
|
||||
"kenyanPhoneDesc": "You are about to pay a Kenyan phone number",
|
||||
"phonePaymentTitle": "Pay {{number}}",
|
||||
"phonePaymentDesc": "Sending to mobile number",
|
||||
"depositFunds": "Deposit Funds",
|
||||
"depositFundsSubtitle": "Add funds to your wallet",
|
||||
"onChainBitcoin": "Bitcoin Address",
|
||||
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Reúne contribuciones de un grupo para un objetivo compartido.",
|
||||
"noContactHead": "{{username}} no encontrado",
|
||||
"noContactDesc": "Agrega a {{username}} a tus contactos para encontrarlo aquí en el futuro, o continúa para enviar un pago ahora.",
|
||||
"kenyanPhoneTitle": "Pagar a {{number}}",
|
||||
"kenyanPhoneDesc": "Estás a punto de pagar a un número de teléfono de Kenia",
|
||||
"phonePaymentTitle": "Pagar a {{number}}",
|
||||
"phonePaymentDesc": "Enviando a un número móvil",
|
||||
"depositFunds": "Depositar fondos",
|
||||
"depositFundsSubtitle": "Agrega fondos a tu billetera",
|
||||
"onChainBitcoin": "Dirección Bitcoin",
|
||||
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Collectez des contributions d’un groupe pour un objectif commun.",
|
||||
"noContactHead": "{{username}} introuvable",
|
||||
"noContactDesc": "Ajoutez {{username}} à vos contacts pour le retrouver ici à l’avenir, ou continuez pour envoyer un paiement maintenant.",
|
||||
"kenyanPhoneTitle": "Payer {{number}}",
|
||||
"kenyanPhoneDesc": "Vous êtes sur le point de payer un numéro de téléphone kényan",
|
||||
"phonePaymentTitle": "Payer {{number}}",
|
||||
"phonePaymentDesc": "Envoi vers un numéro de mobile",
|
||||
"depositFunds": "Déposer des fonds",
|
||||
"depositFundsSubtitle": "Ajoutez des fonds à votre portefeuille",
|
||||
"onChainBitcoin": "Adresse Bitcoin",
|
||||
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Raccogli contributi da un gruppo per un obiettivo condiviso.",
|
||||
"noContactHead": "{{username}} non trovato",
|
||||
"noContactDesc": "Aggiungi {{username}} ai tuoi contatti per trovarlo qui in futuro, oppure continua per inviare un pagamento adesso.",
|
||||
"kenyanPhoneTitle": "Paga {{number}}",
|
||||
"kenyanPhoneDesc": "Stai per pagare un numero di telefono keniota",
|
||||
"phonePaymentTitle": "Paga {{number}}",
|
||||
"phonePaymentDesc": "Invio a un numero di cellulare",
|
||||
"depositFunds": "Deposita fondi",
|
||||
"depositFundsSubtitle": "Aggiungi fondi al tuo wallet",
|
||||
"onChainBitcoin": "Indirizzo Bitcoin",
|
||||
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Colete pagamentos para um objetivo",
|
||||
"noContactHead": "{{username}} não encontrado",
|
||||
"noContactDesc": "Adicione {{username}} aos seus contatos para encontrá-lo aqui no futuro, ou continue para enviar um pagamento agora.",
|
||||
"kenyanPhoneTitle": "Pagar {{number}}",
|
||||
"kenyanPhoneDesc": "Você está prestes a pagar um número de telefone queniano",
|
||||
"phonePaymentTitle": "Pagar {{number}}",
|
||||
"phonePaymentDesc": "Enviando para número de celular",
|
||||
"depositFunds": "Depositar fundos",
|
||||
"depositFundsSubtitle": "Adicione fundos à sua carteira",
|
||||
"onChainBitcoin": "Endereço Bitcoin",
|
||||
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Собирайте взносы от группы для общей цели.",
|
||||
"noContactHead": "{{username}} не найден",
|
||||
"noContactDesc": "Добавьте {{username}} в свои контакты, чтобы находить здесь в будущем, или продолжите, чтобы отправить платёж сейчас.",
|
||||
"kenyanPhoneTitle": "Оплатить {{number}}",
|
||||
"kenyanPhoneDesc": "Вы собираетесь оплатить кенийский номер телефона",
|
||||
"phonePaymentTitle": "Оплатить {{number}}",
|
||||
"phonePaymentDesc": "Отправка на мобильный номер",
|
||||
"depositFunds": "Пополнить баланс",
|
||||
"depositFundsSubtitle": "Добавьте средства в ваш кошелёк",
|
||||
"onChainBitcoin": "Bitcoin-адрес",
|
||||
|
||||
@@ -1005,8 +1005,8 @@
|
||||
"poolsDescription": "Samla in bidrag från en grupp till ett gemensamt mål.",
|
||||
"noContactHead": "{{username}} hittades inte",
|
||||
"noContactDesc": "Lägg till {{username}} i dina kontakter för att hitta dem här i framtiden, eller fortsätt för att skicka en betalning nu.",
|
||||
"kenyanPhoneTitle": "Betala {{number}}",
|
||||
"kenyanPhoneDesc": "Du är på väg att betala ett kenyanskt telefonnummer",
|
||||
"phonePaymentTitle": "Betala {{number}}",
|
||||
"phonePaymentDesc": "Skickar till mobilnummer",
|
||||
"depositFunds": "Sätt in pengar",
|
||||
"depositFundsSubtitle": "Lägg till pengar i din plånbok",
|
||||
"onChainBitcoin": "Bitcoin-adress",
|
||||
|
||||
Reference in New Issue
Block a user