Agency-Phone Apps Personalizados
Agora com o AgencyOS27: uma atualização grátis para quem já tem o telefone.

🧩 Criar uma app própria para o Agency-Phone
Há duas maneiras de pôr uma app no telefone. Escolhe a que te serve:
| Método | Ideal para | O que alteras |
|---|---|---|
| A. SDK (resource externo) | Criadores e scripts que adicionam uma app com o seu próprio ícone | Só o teu próprio resource. Nenhuma alteração ao agency-phone. |
| B. Drop-in (dentro do agency-phone) | Donos de servidor que adicionam um ecrã nativo na interface do telefone | config.lua + apps/ (+ html/js para um ecrã) |
Se distribuis uma app para outros servidores, usa o método A. Não precisa de código-fonte nosso e continua a funcionar depois das atualizações.
A SDK: registar uma app a partir do teu próprio resource
O agency-phone disponibiliza exports com que qualquer resource coloca, em tempo de execução, uma app com o seu próprio ícone no ecrã inicial do telefone. A interface da tua app fica no teu resource; o telefone mostra o ícone e passa-te o controlo quando o jogador lhe toca. Sem alterações ao agency-phone, sem build desbloqueado.
Exemplo mínimo
-- client side of YOUR resource
CreateThread(function()
Wait(1500) -- let agency-phone boot
exports['agency-phone']:RegisterApp({
id = 'myshop',
label = 'My Shop',
icon = 'fa-solid fa-cart-shopping', -- FontAwesome 6 class
-- image = 'https://cfx-nui-myresource/icon.png', -- or your own image instead
color = '#ff9500',
colorDark = '#e08600',
})
end)
-- fired when the player taps your icon on the phone
AddEventHandler('agency-phone:client:openApp', function(appId)
if appId ~= 'myshop' then return end
exports['agency-phone']:ClosePhone()
-- open your own NUI here
end)
Configuração do RegisterApp
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | sim | Id único, em minúsculas, sem espaços. |
label | string | sim | Nome mostrado por baixo do ícone. |
icon | string | não | Classe do FontAwesome 6. Ignorada se image estiver definido. |
image | string | não | Imagem de ícone própria, por exemplo https://cfx-nui-<resource>/icon.png. |
color / colorDark | string | não | Gradiente atrás do ícone (hex). |
slot | number | não | Posição no ecrã inicial. Se faltar, é colocada automaticamente. |
storeDesc | string | não | Descrição curta na página da Store da tua app. Se faltar, usa o label. |
Exports do SDK (cliente)
| Export | Descrição |
|---|---|
RegisterApp(config) | Adiciona ou atualiza a tua app. Devolve true, ou false, err. |
UnregisterApp(id) | Remove a tua app. Também acontece automaticamente quando o teu resource para. |
GetRegisteredApps() | Tabela de todas as apps externas registadas. |
ClosePhone() | Fecha o telefone, por exemplo antes de abrires a tua própria NUI. |
O evento ao tocar
Quando o jogador toca no teu ícone, o agency-phone dispara estes dois eventos no cliente. Usa o que preferires:
| Evento | Argumentos |
|---|---|
agency-phone:client:openApp | appId (filtra pelo teu id) |
agency-phone:client:openApp:<yourId> | nenhum (já é só para a tua app) |
Adiciona dependency 'agency-phone' ao teu fxmanifest.lua para a ordem de arranque estar certa.
B Drop-in, um ecrã nativo dentro do telefone
Para um ecrã desenhado pelo próprio telefone (não pelo teu resource). Isto altera ficheiros do agency-phone.
| Passo | Ficheiro | Editável no build escrow? |
|---|---|---|
1. Entrada da app em AgencyConfig.Apps | config.lua | ✓ sim |
2. Lógica com RegisterNUICallback | apps/<id>_client.lua (+ _server.lua) | ✓ sim |
3. Ecrã <div id="app-<id>"> | html/index.html | ✗ cifrado |
4. case '<id>': em openApp() + carregador | html/js/phone.js | ✗ cifrado |
Os passos 3 e 4 estão em ficheiros cifrados pelo escrow, por isso um ecrã nativo precisa de um build com código-fonte ou desbloqueado. Se só tens a versão escrow, usa o método A (o teu ecrã fica no teu próprio resource). Ficheiros de referência dentro do resource: apps/testapp_client.lua e apps/dispatch_client.lua.
📋 Outros exports do telefone
Cliente
| Export | Devolve | Descrição |
|---|---|---|
IsPhoneOpen() | boolean | Telefone aberto neste momento. |
IsPhonePoweredOn() | boolean | Aparelho ligado. |
HasPhone() | boolean | O jogador tem um item de telefone. |
HasCarrierContract() | boolean | eSIM ou contrato ativo. |
GetPhoneData() | table | Instantâneo do estado do telefone. |
PhoneNotification(title, text, icon, color, timeout, acceptIcon, denyIcon) | void | Notificação no telefone. |
StartCall(contact) | void | Liga para { name, number }. |
StartAgencyPayCheckout(opts) | void | Pagamento AgencyPay (ver abaixo). |
Servidor
| Export | Descrição |
|---|---|
SendMail(identifier, mailData) | Entrega um mail a um jogador (online ou offline). |
AddChirp(data) | Publica uma mensagem no feed social Pulse. |
VerifyAgencyPayCharge(source, requestId, minAmount) | Confirma no servidor um pagamento AgencyPay antes de entregares a mercadoria (ver abaixo). |
⇄ Ponte NUI ↔ Lua (método B)
| Direção | Lado JS | Lado Lua |
|---|---|---|
| JS → Lua | fetchNUI('callbackName', data) | RegisterNUICallback('callbackName', fn) |
| Lua → JS | escuta action em window.onmessage | SendNUIMessage({ action = '...' }) |
💳 Pagamento AgencyPay
Cobra na wallet do jogador a partir de qualquer resource.
exports['agency-phone']:StartAgencyPayCheckout({
merchant = 'My Shop', title = 'Agency Pay', subtitle = 'Checkout',
label = 'Premium Coffee', amount = 24.50,
icon = 'fa-solid fa-mug-hot', accent = '#ff9f0a',
clientEvent = 'myshop:client:agencyPayResult',
serverEvent = 'myshop:server:agencyPayResult',
metadata = { orderId = 'order_2048' },
})
RegisterNetEvent('myshop:client:agencyPayResult', function(result)
if result.status == 'paid' then
-- deliver the goods
end
end)
O resultado inclui status (paid / cancelled / failed), amount, cardId, balance e os teus metadata.
Um paid que chega do cliente ao teu resource não prova nada por si só. Passa o teu próprio id a StartAgencyPayCheckout e chama exports['agency-phone']:VerifyAgencyPayCharge(source, id, amount) no servidor antes de entregares seja o que for. Só devolve true e o valor se este jogador pagou esse pagamento nos últimos 60 segundos, e consome o pagamento para que não possa ser reclamado duas vezes.
🐛 Resolução de problemas
| Sintoma | Solução |
|---|---|
| O ícone não aparece (SDK) | Atualiza o agency-phone para o build mais recente do Keymaster, usa Wait(1500) antes de RegisterApp e adiciona dependency 'agency-phone' ao teu manifest. |
| Tocar não faz nada (SDK) | O teu handler de agency-phone:client:openApp tem de corresponder exatamente ao teu id (distingue maiúsculas e minúsculas). |
| A imagem própria não aparece | Usa um URL NUI completo https://cfx-nui-<your-resource>/path/icon.png e lista o ficheiro em files { } no teu manifest. |
| O ícone não aparece (drop-in) | enabled = true e installed = true na entrada da config. |
Dúvidas ou precisas de ajuda? Junta-te ao Discord da Agency Scripts.