Lingua

Contattami

SaaS · dal 2025

Botoscope®

SaaS che trasforma un negozio WooCommerce in un bot Telegram con catalogo, carrello, checkout e ordini sincronizzati con il negozio.

Lanciato
2025
Stato
Chiuso nel 2026
Piattaforma
Telegram
Store
WooCommerce
  • Node.js (ES modules)
  • TDLib via tdl / tdl-tdlib-addon
  • MySQL / MariaDB (knex, mysql2)
  • Redis (ioredis)
  • PM2
  • axios
  • moment
  • Telegram Web Apps
  • WooCommerce via Botoscope proxy
Apri botoscope.com botoscope.com

Chi sono

Botoscope gestisce un negozio WooCommerce come bot Telegram. Lo sviluppo è iniziato nel 2024; il servizio è stato lanciato pubblicamente nel 2025. I clienti sfogliano le categorie, scelgono le variazioni, riempiono il carrello, completano il checkout e pagano dentro la chat, e l'ordine arriva in WooCommerce. Il motore descritto qui è il processo Node.js che serve un bot. Comunica con Telegram tramite TDLib in modalità bot, con il negozio tramite un proxy Botoscope, mantiene una copia MySQL del catalogo e conserva lo stato per chat in Redis.

Gran parte della difficoltà viene dalla chat stessa. Gli ID dei messaggi cambiano dopo l'invio, le tastiere di risposta non si possono modificare, ogni pressione di un pulsante è un breve payload di callback, il testo digitato può essere un input oppure l'etichetta di un menu, e i vecchi messaggi si accumulano. Gran parte del motore esiste per far comportare un flusso di messaggi come le schermate di un'app.

Il resto è coerenza con un negozio su un altro server: un'importazione del catalogo che non viene eseguita all'avvio, aggiornamenti per singolo prodotto inviati da WordPress, giacenze e prezzi che coincidono con quelli del negozio, un ordine WooCommerce per ogni checkout che segue le modifiche del cliente, e pagamenti tramite processori di carte, gateway crypto e Telegram Stars registrati su quell'ordine.

Botoscope · negozio in Telegram

cosa è stato risolto

CHIUSOdal 2025

Motore e modello dei processi

nel prodotto

Cosa offreOgni negozio ha ottenuto il proprio bot, separato dagli altri negozi, che ha funzionato per settimane su un piccolo server e ha recepito le modifiche fatte dal titolare nel negozio.

Problema
Ogni negozio ha bisogno del proprio bot con la propria sessione Telegram, il proprio catalogo e le proprie impostazioni, da un'unica base di codice. Il processo deve restare in esecuzione per settimane su server piccoli e accettare comandi dal lato del negozio.
Soluzione

Un'istanza del motore per bot: un client TDLib, una copia MySQL del catalogo e lo stato in Redis, configurati tramite ambiente e avviati sotto PM2 con limiti di memoria. I moduli comunicano tramite un event bus e registri di hook; le funzioni a pagamento vengono caricate come estensioni; il lato negozio raggiunge il bot tramite un piccolo endpoint HTTP di comandi.

Stack
  • Node.js
  • TDLib (tdl)
  • EventEmitter
  • PM2
  • knex
  • ioredis
Attività · 6
Isolare i negozi senza un percorso di codice multi-tenantUn processo per bot; token, dominio del negozio, chiavi e limiti del piano arrivano dall'ambiente

Nel motore non esiste alcun ID di tenant. init() effettua l'accesso di un client bot TDLib, apre un pool knex e una connessione Redis, e legge dalle variabili d'ambiente il dominio del negozio, la chiave API del client, il limite di prodotti e il flag del piano gratuito. L'isolamento tra i negozi è una questione di deployment: ogni bot ha la propria istanza con la propria directory del database TDLib e il proprio schema MySQL.

Questo mantiene semplici le query e le chiavi Redis (chat:<id>:<key>) al prezzo di un processo per negozio. Le chiavi Redis non hanno prefisso del bot, quindi due istanze non devono condividere lo stesso database Redis.

Far girare il bot su TDLib invece che sulla Bot API HTTPClient TDLib in modalità bot: una connessione persistente, un solo flusso di aggiornamenti per entrambe le direzioni, cache locali

Il bot accede tramite TDLib (tdl con tdl-tdlib-addon) con un token del bot e mantiene un'unica connessione di lunga durata con Telegram. Gli aggiornamenti vengono inviati a client.on('update'), quindi il motore non ha bisogno di un URL di webhook raggiungibile da Telegram né di un ciclo di polling getUpdates.

Il motivo principale è il modello a schermate. TDLib segnala i messaggi in uscita del bot stesso come updateNewMessage e updateMessageSendSucceeded nello stesso flusso dei messaggi del cliente. Un unico gestore registra ogni ID di messaggio in una chat privata, inviato o ricevuto, e quando una schermata cambia il motore li elimina con deleteMessages (revoke: true). La chat conserva solo la schermata corrente, e gli input digitati, come una ricerca o un indirizzo, scompaiono con essa. Lo stesso flusso permette al motore di attendere che un album di foto sia effettivamente arrivato prima di inviare il pannello del prodotto.

TDLib copre anche chiamate che la Bot API HTTP non ha: getMessage rilegge il testo di un messaggio inviato in precedenza dal bot, cosa che il pannello dei prodotti raggruppati usa per ridisegnare i suoi pulsanti. getChat e getUser trovano risposta nel client TDLib con il database delle informazioni di chat attivo, e i contenuti multimediali partono da percorsi locali o tramite ID di file remoto attraverso il database dei file. Il database dei messaggi è spento perché il bot non conserva cronologia, l'ottimizzatore dello storage elimina i file di TDLib e TDLib esegue un solo thread di lavoro.

Il costo è che la semantica di TDLib trapela nell'applicazione: gli ID dei messaggi sono temporanei finché il server non li conferma (vedi il task sulla mappatura degli ID), e la directory di sessione lega un bot a un solo processo.

Possibili passi successivi che TDLib già offre: attivare il suo database dei messaggi per cronologia e ricerca per chat, e inviare file più grandi del limite di upload di 50 MB della Bot API HTTP. Il motore oggi non fa né l'uno né l'altro; limita i documenti a 50 MB.

Molti moduli reagiscono agli stessi aggiornamenti e alle stesse schermateEvent bus per gli aggiornamenti più registri di hook per chat e globali ordinati per priorità

Answer trasforma gli aggiornamenti TDLib in eventi del bus: do_callback_query per la pressione dei pulsanti, do_command per il testo, pre_checkout_query e successful_payment per i pagamenti. Carrello, preferiti, prenotazioni, assistenza e il resto si iscrivono e gestiscono solo le proprie azioni, così non importano i gestori degli altri.

Per la composizione dell'interfaccia ci sono due registri di hook. Hooks conserva le callback per chat_id (il menu contestuale corrente, i pulsanti extra del prodotto) ordinate per priorità, dove aggiungere un hook con una priorità esistente lo sostituisce. GlobalHooks fa lo stesso per tutte le chat; l'estensione di assistenza lo usa per aggiungere un pulsante ai pannelli prodotto che non le appartengono. Gli hook per chat vengono eliminati quando la chat viene cancellata.

Attivare e disattivare le funzioni del piano per negozioElenco delle estensioni sincronizzato dal negozio; classe costruita dallo slug; riavvio PM2 per scaricare

L'elenco delle estensioni attive (coupon, spedizioni, assistenza, prenotazioni, marketing, b2b e altre) viene sincronizzato nella tabella delle opzioni. Extensions.init() converte ogni slug in un nome di classe e lo istanzia prima che i moduli principali vengano costruiti, così i loro hook esistono per primi. I moduli principali verificano la presenza di un'estensione tramite getter facoltativi e cambiano comportamento; per esempio il checkout salta il passo del codice promozionale quando i coupon sono disattivati.

Disattivare un'estensione non può scaricare il codice che ha già registrato dei listener, quindi azzerare la cache delle estensioni riavvia il processo tramite PM2. I commenti nel codice osservano che questo interrompe gli utenti a metà sessione.

Permettere al lato negozio di inviare modifiche e query a un bot in esecuzioneEndpoint HTTP /command; comandi noti gestiti direttamente, gli altri instradati ai moduli tramite il bus

Commander esegue un semplice server http con /health e /command. I comandi noti (payment_success, order_is_done, reset_cache, update_product_cache) sono gestiti direttamente. Qualsiasi altro viene emesso come exec_command; il modulo che lo possiede scrive is_executed e answer nell'oggetto body condiviso, che l'endpoint restituisce dopo una breve attesa fissa.

reset_cache aggiorna una cache di impostazioni o tutte, con le estensioni sempre per ultime perché quella riavvia il processo. Il reset_db_cache() di ogni modulo rilegge il proprio blob JSON nella tabella delle opzioni e svuota la propria copia in memoria.

Mantenere in salute un processo di lunga durata su un server piccoloPM2 in modalità fork, heap di 450 MB, riavvio a 1 GB, da 2 a 5 connessioni al DB, azzeramenti temporizzati delle cache

ecosystem.config.cjs esegue una singola istanza fork (la modalità cluster è commentata; un token del bot significa una sessione TDLib), limita l'old space di V8, riavvia in caso di crescita della memoria e richiede 30 s di uptime prima che un avvio sia considerato stabile. I gestori di SIGTERM, SIGINT e SIGQUIT distruggono il pool knex prima dell'uscita, e un timer registra nel log l'uso del pool quando c'è attività.

Le cache in memoria, come carrelli e valute selezionate, vengono svuotate con dei timer, e la mappa degli ID dei messaggi viene svuotata a ogni cambio di schermata.

La chat come interfaccia di un'applicazione

nel prodotto

Cosa offreI clienti si muovevano tra catalogo, carrello e checkout come tra le schermate di un'app, e la chat restava in ordine invece di riempirsi di vecchi messaggi.

Problema
Una chat Telegram è un flusso di messaggi, ma il prodotto ha bisogno di schermate: una pagina del catalogo, un carrello, un passo del checkout, ciascuna che sostituisce la precedente, con pulsanti che si aggiornano sul posto.
Soluzione

Tracciare ogni messaggio per chat, cambiare le schermate eliminando l'insieme precedente dopo aver disegnato il nuovo, mappare gli ID temporanei di TDLib agli ID del server per le modifiche successive, e mantenere lo stato di navigazione in un contesto per chat in Redis.

Stack
  • TDLib
  • Redis
  • Tastiere di risposta e inline di Telegram
Attività · 6
Modificare un messaggio il cui ID cambia dopo l'invioMappare gli ID temporanei agli ID del server da updateMessageSendSucceeded, per chat in RAM

sendMessage in TDLib restituisce un ID locale temporaneo; l'ID reale arriva più tardi in updateMessageSendSucceeded insieme a old_message_id. Il motore memorizza l'ID restituito da send() nel contesto della chat (top_message_id, taxonomies_btn_mgs_id e simili) e lo risolve tramite MessageMapping quando deve modificare o eliminare quel messaggio.

La mappatura prima stava in Redis ed è stata spostata in una mappa per chat in memoria, svuotata a ogni cambio di schermata per restare piccola. Senza di essa, modificare una tastiera inline dopo un clic punterebbe a un ID che non esiste più.

Far comportare un gruppo di messaggi come una sola schermataRegistrare in Redis gli ID inviati; a una nuova schermata metterli da parte ed eliminarli dopo il disegno

Ogni nuovo ID di messaggio in una chat privata viene aggiunto a chat:<id>:messages. make_messages_castling() sposta quell'elenco in chat:<id>:old_messages e azzera la mappatura degli ID, il modulo disegna la nuova schermata e clear_display() elimina poi i vecchi messaggi. Il nuovo contenuto compare prima che il vecchio scompaia, quindi la chat non resta mai vuota tra una schermata e l'altra.

Una pulizia completa della chat è stata provata e conservata nel codice solo come avvertimento: su alcuni telefoni faceva uscire l'utente dal bot.

I messaggi con tastiera di risposta non si possono modificareInviare un nuovo messaggio superiore con la tastiera aggiornata, poi eliminare il vecchio

Il menu persistente in basso mostra un pulsante del carrello con il conteggio degli articoli in tempo reale. Telegram non permette di modificare il testo di un messaggio che porta una tastiera di risposta, quindi Cart.redraw_menu() invia un nuovo messaggio superiore ed elimina il precedente tramite il suo ID mappato, mentre il riepilogo in basso, un messaggio inline, viene modificato sul posto dopo un breve ritardo.

Il menu corrente è conservato come hook context_menu per chat. change_menu_buttons_text() riscrive solo il pulsante che inizia con l'emoji del carrello nel menu serializzato, così qualsiasi schermata può aggiornare il conteggio del carrello senza conoscere il proprio layout.

I testi del negozio sono HTML; TDLib vuole testo semplice con offset delle entitàConvertitore proprio: decodifica le entità, elimina un sottoinsieme di tag, emette gli offset di textEntity

format_string_before_send() gestisce strong, em, u, s, code, pre e a href. Trova il tag di apertura più vicino, lo rimuove insieme al tag di chiusura e registra le posizioni di inizio e fine nella stringa ripulita; queste diventano gli offset di textEntity in formattedText. Le entità HTML con nome e numeriche provenienti da WooCommerce vengono decodificate per prime. Un tag malformato o non chiuso fa sì che la funzione restituisca il testo originale senza entità invece di inviare una formattazione rotta.

Accanto ci sono due trucchi di impaginazione: una serie di caratteri Braille vuoti riempie i messaggi perché le schede dei prodotti abbiano la stessa larghezza, e un carattere combinante di sovrapposizione disegna un vecchio prezzo barrato dentro le etichette dei pulsanti, dove le entità non sono ammesse.

Il testo digitato può essere un input dell'utente o un pulsante di menu localizzatoFlag per chat per l'input atteso; le etichette dei pulsanti note interrompono sempre l'attesa

I pulsanti della tastiera di risposta arrivano come testo semplice nella lingua dell'utente, quindi l'instradamento confronta il testo in arrivo con le etichette costruite da get_menu_command_text() per quella chat. Quando un passo attende testo libero (ricerca, SKU, indirizzo, codice promozionale, messaggio all'assistenza), il contesto contiene is_waiting_user_input e il comando da riprendere. Answer salva il testo come user_message_text e riesegue quel comando, a meno che il testo sia un comando con slash o un'etichetta di navigazione.

I gestori di input del checkout ripetono il controllo rispetto alle etichette dei propri passi, così premere Indietro mentre è atteso un indirizzo torna indietro invece di salvare quella parola come indirizzo.

Le schede dei prodotti devono arrivare in ordine e sopravvivere a una paginazione rapidaAttendere l'aggiornamento dell'album prima del suo pannello; consentire una sola azione di paginazione alla volta

Una scheda prodotto è un album multimediale seguito da un pannello di testo con pulsanti. print_products_blocks() invia l'album, attende fino a 10 s l'evento new_message_coming di quella chat, poi fa una pausa prima di inviare il pannello, così il pannello non finisce mai sopra le sue foto. Brevi pause fisse prima di ogni invio e di ogni eliminazione distribuiscono le richieste nel tempo.

Toccare rapidamente avanti e indietro avvierebbe altrimenti un secondo cambio di schermata prima che il primo abbia eliminato i suoi messaggi. Un flag nowdo per chat ignora la navigazione mentre una schermata viene sostituita e viene rilasciato un secondo dopo l'eliminazione dei vecchi messaggi.

Sincronizzazione del catalogo WooCommerce

nel prodotto

Cosa offreI clienti scorrevano rapidamente il catalogo e vedevano prezzi e giacenze aggiornati. Le modifiche nel negozio raggiungevano il bot da sole, e gli iscritti venivano avvisati quando gli articoli tornavano disponibili.

Problema
Il negozio gira su un sito WordPress su un altro server. Il bot deve scorrere rapidamente il catalogo, mostrare prezzi e giacenze aggiornati e recepire le modifiche senza interrogare il negozio a ogni tocco.
Soluzione

Mantenere una copia MySQL dei prodotti e dei collegamenti alle categorie, riempita da un'importazione completa a pagine e aggiornata per singolo prodotto quando il negozio invia una modifica. Le impostazioni del negozio arrivano come blob JSON in una tabella delle opzioni con cache in memoria.

Stack
  • MySQL (MyISAM)
  • knex
  • WooCommerce REST via proxy
  • Telegram Web Apps
Attività · 6
Raggiungere i dati di ogni negozio senza custodire le sue credenziali RESTTutte le chiamate al negozio vanno a un unico proxy; dominio, endpoint, chiave API e HMAC passano nelle intestazioni

get_rest_data() e send_remote_data() inviano in POST a un unico URL del proxy Botoscope. Il negozio è indicato in un'intestazione client-domain, l'endpoint in un'altra, e la richiesta è autenticata con la chiave API del client e un pass derivato con HMAC-SHA256. I download di file usano lo stesso proxy tramite il suo endpoint fetch-file.

Il motore non ha mai bisogno delle chiavi REST del negozio, e il lato WordPress deve solo rispondere al proxy. Gli errori restituiscono null e i chiamanti trattano null come assenza di dati, così un'interruzione del negozio degrada in elenchi vuoti invece di mandare in crash una schermata.

Importare un catalogo completo su richiesta, non a ogni riavvioSvuotare, disattivare le chiavi, scaricare pagine da 100, inserire a lotti, riattivare le chiavi in finally

Products.reset_db_cache() legge il numero di prodotti, svuota products e products_vs_terms, esegue ALTER TABLE ... DISABLE KEYS su entrambe le tabelle MyISAM e scarica products/{offset}/{limit} in pagine da 100. Ogni pagina viene inserita in lotti di 100 righe; un errore su una pagina viene registrato nel log e l'importazione passa alla pagina successiva. MyISAM non ha transazioni, quindi le righe già scritte da una pagina fallita restano. Le chiavi vengono riattivate in un blocco finally, così un'importazione fallita lascia comunque tabelle indicizzate. Mentre l'importazione è in corso, il bot mostra la parte di catalogo caricata finora.

L'importazione non viene eseguita all'avvio. Un commento spiega che con un catalogo grande potrebbe bloccare il server a ogni riavvio, quindi parte solo con un comando reset_cache esplicito dal negozio.

I payload dei prodotti variano per tipo e continuano a crescereColonne per ciò che viene filtrato o ordinato, tutto il resto in una colonna JSON data

insert_product_batch() destruttura in colonne i campi che il motore interroga (price, sale_price, flag di giacenza, type, parent_id, publish_date, menu_order, minimo, massimo e passo del carrello) e serializza il resto del payload in data. Elenchi multimediali, download, giorni di accesso o l'URL di un prodotto esterno possono essere aggiunti dal lato del negozio senza modifiche allo schema. Gli ID delle categorie vanno sia in un elenco separato da virgole sulla riga sia in products_vs_terms per ricerche indicizzate per categoria.

install.sql viene eseguito a ogni avvio con CREATE TABLE IF NOT EXISTS, così una nuova istanza predispone da sola il proprio schema.

Applicare le modifiche al singolo prodotto senza una reimportazione completaIl negozio invia update_product_cache; prodotto, variazioni e termini riscritti da una sola risposta del negozio

Quando un prodotto cambia in WooCommerce, il lato negozio invia update_product_cache a /command. update_product_cache() recupera products_filter/{id}, che restituisce il prodotto con le sue variazioni, poi elimina la riga del prodotto, le righe con quel parent_id e i suoi collegamenti alle categorie, e inserisce le nuove righe. Una richiesta porta il prodotto e le sue variazioni, quindi le righe vengono riscritte una sola volta per modifica.

Il limite del piano conta solo i prodotti genitore. L'importazione completa riduce i prodotti principali per pagina e ne mantiene sempre le variazioni; il percorso incrementale salta un nuovo prodotto quando il numero di prodotti distinti in products_vs_terms raggiunge il limite.

I flag di giacenza si disallineano e i clienti vogliono avvisi di ritorno disponibileRiconciliare i flag di disponibilità dalla quantità ogni 3 min; avvisare gli iscritti ogni 2 h

Per i prodotti con giacenza gestita, un timer ogni tre minuti imposta is_in_stock da stock_quantity in entrambe le direzioni, così il pulsante di acquisto segue la quantità effettiva anche quando un flag inviato era obsoleto. I clienti possono iscriversi a un prodotto esaurito o a una singola variazione; stock_notify memorizza product_id e chat_id. Ogni due ore il motore trova i prodotti con iscritti tornati disponibili, invia di nuovo la scheda a ciascun iscritto e rimuove l'iscrizione.

Ogni query sul catalogo confronta inoltre publish_date con l'ora corrente, così i prodotti programmati restano nascosti fino alla loro data.

Il filtraggio a faccette non sta nei pulsanti di una chatAprire la pagina dei filtri del negozio come Telegram Web App; rimanda gli ID corrispondenti

Il comando /filter e i pulsanti Filtro aprono filter_page_url come Web App con il chat_id aggiunto. Il filtraggio avviene sul lato WordPress. Il risultato torna al motore come comando filtered_products_ids su /command, e Products stampa quegli ID come un normale catalogo paginato in quella chat.

Questo mantiene in un solo posto, sul negozio, il conteggio delle faccette e la logica degli attributi, mentre la chat mostra comunque i risultati nel proprio formato di scheda.

Carrello, variazioni e checkout

nel prodotto

Cosa offreIl bot seguiva le regole del negozio per quantità, variazioni, prenotazioni e sconti, e l'ordine nel negozio corrispondeva a ciò che il cliente aveva visto nella chat.

Problema
Carrello e checkout devono far rispettare le regole del negozio (passi di quantità, minimi, giacenza, variazioni, fasce di prenotazione, sconti) con soli pulsanti e brevi risposte di testo, e l'ordine WooCommerce deve corrispondere a ciò che il cliente ha visto.
Soluzione

I carrelli vivono in Redis dietro una cache RAM, le variazioni si scelgono con un selettore inline a cascata e il checkout è una macchina a passi sfoltita in base al contenuto del carrello e alle estensioni attive. L'ordine viene creato una volta e aggiornato man mano che il cliente lo modifica.

Stack
  • Redis
  • Node.js
  • moment
  • WooCommerce orders via proxy
Attività · 6
Il carrello viene letto a quasi ogni renderingRedis per chat dietro una cache RAM write-through; articoli scaduti ed eliminati rimossi

Cart.get() risponde da una mappa statica per chat e ripiega su Redis (chat:<id>:cart); update() scrive in entrambi. L'etichetta del pulsante del carrello, i totali e ogni pannello prodotto leggono il carrello, quindi la cache risparmia ogni volta un viaggio a Redis. Gli articoli possono avere una scadenza, usata dai blocchi di prenotazione, e vengono scartati in lettura una volta scaduti. Disegnare il carrello rimuove prima gli ID che non esistono più nel catalogo locale, così un prodotto eliminato in WooCommerce non può arrivare al checkout.

I pulsanti della quantità rispettano quantity_step, min_cart_count e la giacenza gestita, e una variazione senza valori propri li eredita dal prodotto genitore.

Scegliere una variazione con i pulsanti quando non tutte le combinazioni esistonoRighe di attributi a cascata; ogni riga offre solo i termini che formano ancora una variazione reale

get_buy_buttons_panel() legge la combinazione di attributi di ogni variazione e ordina gli attributi secondo l'ordine degli attributi del negozio. La prima riga elenca ogni termine in uso; ogni riga successiva elenca solo i termini che, insieme alle scelte precedenti, corrispondono ad almeno una variazione esistente. Un attributo con tre o più opzioni può essere reso come una compatta riga precedente / corrente / successiva invece di una griglia.

Cambiare un attributo superiore azzera quelli inferiori e si posiziona sulla prima combinazione corrispondente. Un prodotto si apre sulla sua variazione più economica. La scelta è memorizzata per chat e prodotto nel contesto, e il pannello viene modificato sul posto a ogni tocco.

I passi del checkout dipendono da ciò che c'è nel carrelloElenco dei passi sfoltito per chat: niente spedizione per i carrelli digitali, niente passo di pagamento quando è gratuito

I passi di base sono codice promozionale, indirizzo di spedizione, metodo di spedizione, metodo di pagamento, commento e conferma. I passi delle estensioni disattivate vengono rimossi all'avvio. get_checkout_steps() ispeziona poi il carrello, percorrendo i prodotti raggruppati e le variazioni dei loro figli variabili per trovare un articolo fisico; un carrello che non ne ha salta entrambi i passi di spedizione. Un totale pari a zero passa al gate regalo e salta la scelta del pagamento, un negozio con un solo gateway lo preseleziona e il pagamento in Stars forza il gate Stars.

I passi obbligatori bloccano next() finché non viene salvato un valore, e qualsiasi passo può essere riaperto in modalità modifica dalla schermata di conferma e tornare direttamente a essa.

L'ordine del negozio deve seguire le modifiche fatte dopo che esisteCreare l'ordine una volta al passo di pagamento, conservarne l'ID nel contesto, inviare ogni modifica successiva

generate_payment_button() chiama create_order solo quando il contesto non ha ancora un order_id, poi memorizza l'ID. Se il cliente torna indietro e cambia metodo di pagamento, indirizzo, codice promozionale o commento, ogni gestore chiama orders.edit(order_id, ...) in modo che l'ordine WooCommerce venga aggiornato invece che duplicato.

L'ordine porta il chat_id Telegram e il nome del bot, così la cronologia degli ordini viene recuperata per chat e un link pay-for-me può in seguito individuare l'ordine.

Due clienti del bot non devono prenotare la stessa fascia orariaBlocchi in memoria per fascia per 600 s, conteggiati rispetto alla capienza residua della fascia

Le fasce arrivano dal negozio per prodotto e per giorno con capacity e capacity_used, e le fasce che il negozio segna come riservate vengono escluse. Quando un cliente sceglie una fascia, il motore registra un blocco con una scadenza e aggiunge la prenotazione al carrello con lo stesso tempo di blocco. is_freezed() conta i blocchi attivi per quella fascia, data e chiave e blocca la fascia quando i blocchi raggiungono la capienza meno i posti usati. Rimuovere l'articolo dal carrello rilascia il suo blocco tramite l'evento done_remove_from_cart.

Le date del calendario e il filtraggio delle fasce passate usano il fuso orario del negozio dalle impostazioni, non l'orologio del server. I blocchi vivono nella memoria del processo del bot, quindi valgono solo per i clienti di questo bot e si azzerano al riavvio; le prenotazioni fatte sul sito web nello stesso momento non vengono bloccate.

Le promozioni come 'ogni 3° articolo scontato del 25%' hanno bisogno di un formato di regolePiccolo linguaggio di formule: condizioni sul numero di articoli; sconto; n-esimo, più economico o più caro, tetto

Le campagne di marketing usano formule come 'c >= 6 & c < 12; 25%; 3n, me, cap=3'. parse_formula() le divide in condizioni sul numero di articoli, uno sconto percentuale o fisso e modificatori (ogni n-esima unità, prima la più economica o la più cara, un tetto). get_applied_amount() espande il carrello in una voce per ogni unità dei prodotti della campagna, applica le formule in ordine e segna le unità usate perché una formula successiva non possa scontarle di nuovo.

Le finestre delle campagne sono controllate nel fuso orario del negozio, le campagne in modalità test si applicano solo alla chat dell'amministratore, e lo sconto della campagna viene saltato quando è già applicato un coupon. I coupon limitati a un prodotto si estendono ai figli dei prodotti raggruppati e variabili.

Pagamenti e valute

nel prodotto

Cosa offreI clienti vedevano i prezzi nella propria valuta e pagavano con carta, crypto o Telegram Stars, oppure chiedevano a un amico di pagare. Il negozio riceveva un ordine pagato.

Problema
Il pagamento parte nella chat, finisce su una pagina del gateway o dentro Telegram, e deve concludersi come ordine WooCommerce pagato, attraverso più gateway e valute di visualizzazione.
Soluzione

Gli importi restano nella valuta base del negozio e vengono convertiti solo per la visualizzazione e l'addebito. Il motore crea da sé le sessioni dei gateway e interroga periodicamente i gateway che non hanno un webhook per il bot.

Stack
  • PayPal (sandbox)
  • Stripe Checkout
  • LiqPay
  • CoinGate
  • CryptoBot
  • Telegram Stars (TDLib invoices)
Attività · 5
Prezzi mostrati in una valuta, addebitati in un'altraImporti mantenuti nella valuta base, convertiti alla visualizzazione e all'addebito, ordini riportati alla normalità

Totali del carrello, sconti e spedizione sono calcolati nella valuta base. format_price() converte nella valuta selezionata della chat con il tasso sincronizzato e applica la posizione del simbolo e i decimali, arrotondando per eccesso quando i centesimi sono nascosti. generate_payment_button() ricalcola il totale nella valuta selezionata prima di chiamare un gateway. Gli ordini recuperati da WooCommerce in un'altra valuta vengono divisi per il loro tasso d'ordine memorizzato, così cronologia e totali coincidono con l'aritmetica del bot.

Quando il negozio non consente di pagare in altre valute, la selezione viene riportata alla valuta base al passo di pagamento e il cliente ne viene informato con il motivo.

Accettare Telegram Stars come metodo di pagamentoStars come valuta XTR del negozio con un tasso; fattura TDLib il cui payload nomina l'ordine

Le Stars sono configurate come valuta chiamata XTR nell'elenco delle valute del negozio, quindi il loro tasso arriva con la stessa sincronizzazione. L'importo è il totale base per il tasso XTR, arrotondato per eccesso. La fattura viene inviata come inputMessageInvoice con un provider token vuoto e un payload JSON in base64 che contiene order_id, chat_id, amount e gateway. La pre-checkout query riceve risposta e, su messagePaymentSuccessfulBot, il payload viene decodificato e il negozio viene avvisato che l'ordine è pagato.

Senza una valuta XTR l'opzione Stars segnala un errore invece di una fattura.

Gateway crypto senza un webhook registrato per il botPolling per chat ogni 8 s fino a 15 min, annullato quando il carrello viene riaperto

Per CryptoBot il motore crea una fattura denominata in valuta fiat con scadenza di 900 s e interroga getInvoices ogni 8 s. Una Map di timer con chiave chat_id mantiene un solo polling attivo per cliente, e aprire il carrello lo annulla. Al pagamento l'ordine viene impostato su processing e il carrello svuotato; alla scadenza o all'annullamento il cliente riceve un messaggio.

CoinGate fa il contrario: il suo URL di callback punta al proxy Botoscope, che inoltra il webhook a /command come payment_success, e l'ordine viene concluso quando il webhook segnala il pagamento.

I gateway di carte hanno ciascuno il proprio formato di sessioneSessioni Stripe e LiqPay costruite nel motore, aperte come Web App o URL; PayPal in sandbox

Ogni gateway ha il proprio costruttore: Stripe tramite una Checkout Session, LiqPay tramite un blob di dati in base64 firmato con SHA-1 su chiave privata, dati e chiave privata. Il link viene inviato come pulsante Web App o URL, facoltativamente preceduto da pulsanti per le politiche di rimborso, privacy e spedizione. Il costruttore PayPal (credenziali client OAuth, poi un pagamento con URL di ritorno e di annullamento) invia il pagamento all'endpoint sandbox di PayPal in questo codice, quindi funziona solo per i test.

I metodi offline (dati della carta bancaria, SWIFT, regalo, pay-for-me) mostrano istruzioni tradotte e un pulsante di conferma invece di un link.

Permettere a un altro utente Telegram di pagare un ordineIl deep link pay-for-me apre l'ordine nella chat di chi paga e avvisa l'acquirente

Scegliere pay-for-me conclude il checkout con un link t.me che avvia il bot con payforme_<order_id>. Quando un altro utente lo apre, il motore carica l'ordine dal negozio, applica la valuta dell'ordine, rimuove i metodi offline e pay-for-me e mostra i gateway rimanenti per il totale dell'ordine. Dopo il pagamento la chat dell'acquirente riceve un messaggio che indica chi ha pagato.

Contenuti multimediali, file e lingue

nel prodotto

Cosa offreFoto e video dei prodotti si aprivano rapidamente, i file acquistati arrivavano direttamente nella chat e i clienti leggevano menu e catalogo nella propria lingua.

Problema
Immagini e file dei prodotti risiedono sul negozio, e inviarli tramite Telegram a ogni visualizzazione è lento. Il bot scarica anche URL che provengono dai dati del negozio. Interfaccia e catalogo devono essere leggibili nella lingua del cliente.
Soluzione

Una cache multimediale locale con hash, download protetti attraverso il proxy, riutilizzo dei file già ospitati da Telegram e traduzioni a livelli: stringhe di interfaccia incluse, override del titolare e traduzioni per campo sui dati del catalogo.

Stack
  • TDLib file API
  • Node streams
  • crypto (MD5, HMAC)
  • JSON language packs
Attività · 6
Riscaricare le foto dei prodotti a ogni visualizzazione di una schedaCache locale con chiave MD5 dell'URL, divisa in cartelle di due caratteri esadecimali, inviata come file locali

send_media_gallery() calcola l'hash di ogni URL multimediale, salva il file in media_files_cache/<primi due caratteri esadecimali>/<hash> al primo utilizzo e in seguito passa a TDLib il percorso locale. I file di zero byte vengono eliminati e saltati, un album è limitato a 8 elementi e una variazione senza immagini proprie ripiega sui contenuti multimediali del prodotto genitore.

I link ai post t.me saltano la cache e vanno a TDLib come file remoti.

Il bot scarica URL presi dai dati del negozioLista consentita di domini, estensioni bloccate, limiti di dimensione per tipo, file parziali rimossi

downloadFileToLocal() accetta solo URL http(s) sul dominio del client o su host Telegram, rifiuta estensioni di eseguibili e script nei link diretti, invia una richiesta HEAD per respingere i file oltre il limite (10 MB per immagini e audio, 50 MB per video e documenti), poi trasmette il file attraverso l'endpoint fetch-file del proxy con lo stesso limite. I download vuoti, troppo grandi o falliti vengono eliminati dal disco.

I video dei prodotti di grandi dimensioni sono lenti da inviare dal negozioPubblicare il video in un canale; il bot risponde con il suo ID file Telegram da riutilizzare

Answer osserva i post del canale. Quando un video compare in un canale di cui il bot fa parte, il bot risponde con l'ID di file remoto del video. Un link a un post t.me o quell'ID possono poi essere usati come contenuto multimediale del prodotto, e il motore lo invia come file remoto, così Telegram lo serve dal proprio storage senza un upload per cliente.

Consegnare file digitali a pagamento dentro la chatDopo il pagamento inviare ogni download attraverso il recupero protetto, in cache con un nome mascherato

Quando il carrello conteneva prodotti scaricabili e l'ordine è pagato online o è gratuito, do_after_pay() invia come documento ogni download di quei prodotti. I file sono messi in cache con un nome derivato dall'ID del download tramite shiftUUID(), così il nome memorizzato non è l'ID del negozio, e i documenti oltre 50 MB vengono rifiutati. I prodotti con accesso limitato mostrano i giorni rimanenti, calcolati dalla data di pagamento dell'ordine.

Le fatture PDF dall'endpoint delle fatture del negozio seguono lo stesso percorso.

Testo dell'interfaccia in 7 lingue, modificabile dal titolare del negozioJSON incluso per lingua, sovrapposto dagli override sincronizzati del titolare, con ripiego sull'inglese

Sette file di lingua (en, es, pt, ru, pl, kz, ua) con le stesse 215 chiavi vengono caricati all'avvio. __() unisce il file di lingua della chat con gli override inseriti dal titolare nell'amministrazione (sincronizzati come interface_translations e indicizzati come s<number>); se una chiave manca ancora, riprova con l'inglese più gli override. La lingua scelta è memorizzata per chat; al primo contatto proviene dal language_code del client Telegram se quella lingua è abilitata, altrimenti dall'impostazione predefinita del negozio.

Poiché i pulsanti del menu vengono abbinati in base al loro testo localizzato, le etichette per una chat vengono risolte in parallelo con Promise.all a ogni controllo di instradamento.

Contenuti del catalogo e ricerca devono funzionare in ogni linguaTraduzioni per campo su prodotti e termini; la ricerca corrisponde al titolo o alle traduzioni

Prodotti, categorie, marchi, campi meta, metodi di spedizione e di pagamento portano un oggetto translations. translate_to() restituisce 0 quando la chat usa la lingua predefinita, così il caso comune salta ogni ricerca; altrimenti get_field_translation() sceglie il campo tradotto e ripiega sull'originale. La ricerca per titolo esegue LIKE sia sulla colonna del titolo sia sul JSON delle traduzioni memorizzate con un minimo di 3 caratteri, così una query in qualsiasi lingua attiva trova il prodotto.

Le date nei meta dei prodotti e i periodi di accesso sono formattati con la lingua della chat come locale.

Numeri

dal prodotto
  • Lingue del bot 7 Inglese, spagnolo, portoghese, russo, polacco, kazako, ucraino.
  • Metodi di pagamento online 5 Stripe, LiqPay, CoinGate, CryptoBot e Telegram Stars.
  • Percorso di risposta push, senza polling TDLib mantiene una connessione persistente con Telegram: ogni tocco raggiunge subito il bot, senza passaggio da webhook e senza ritardo di polling.