LAKSHMI COURSES — SCHEMA DI INTEGRAZIONE FILEMAKER Happy Brain — Contratto 0.4.0 — 10 settembre 2026 Modelli: centri, listino completo, lettura eventi e ricevute ACK. Esempio didattico da ricostruire nello Script Workspace, non un file importabile automaticamente e non uno script collaudato nell'installazione del cliente. Usa i nomi inglesi dei passi/funzioni: adattare lingua e versione installate. La verifica JSONGetElementType proposta richiede FileMaker 19.5 o successivo. Verificare anche la disponibilità del nome corrente Get(LastErrorDetail). PREREQUISITI - $apiUser e $applicationPassword provengono dalla configurazione protetta. - Non scrivere credenziali letterali nello script o nei log. - Usare solo un codice sintetico concordato, non un centro reale. - Il codice sorgente deve essere un campo TESTO. Un numero convertito dopo in testo non recupera gli zeri iniziali già persi. - Il contatore revision deve essere persistente, intero positivo e crescente per centro. Conservare il payload finché il risultato non è confermato. 1. RICHIESTA DI UN CENTRO SINTETICO Set Error Capture [ On ] Set Variable [ $url ; Value: "https://staging-corsi.lakshmi.it/wp-json/lakshmi/v1/sync/customers" ] Set Variable [ $centreCode ; Value: "DEMO-FM-000123" ] Set Variable [ $requestBody ; Value: JSONSetElement ( "{}" ; [ "customers[0].filemaker_code" ; $centreCode ; JSONString ] ; [ "customers[0].name" ; "DEMO Centro Aurora" ; JSONString ] ; [ "customers[0].email" ; "aurora@example.test" ; JSONString ] ; [ "customers[0].price_tier" ; "demo_contracted" ; JSONString ] ; [ "customers[0].is_contractor" ; 1 ; JSONBoolean ] ; [ "customers[0].enabled" ; 1 ; JSONBoolean ] ; [ "customers[0].revision" ; 1 ; JSONNumber ] ) ] Set Variable [ $responseBody ; Value: "" ] Set Variable [ $responseHeaders ; Value: "" ] Set Variable [ $curlOptions ; Value: "--request POST --basic --user " & Quote ( $apiUser & ":" & $applicationPassword ) & " --header " & Quote ( "Content-Type: application/json" ) & " --header " & Quote ( "Accept: application/json" ) & " --data @$requestBody" & " --dump-header $responseHeaders" & " --connect-timeout 10 --max-time 30 --show-error" ] Insert from URL [ Select ; With dialog: Off ; Target: $responseBody ; $url ; Verify SSL Certificates ; cURL options: $curlOptions ] # Catturare entrambi i valori nel PRIMO passo dopo Insert from URL. Set Variable [ $transfer ; Value: Let ( [ fmError = Get ( LastError ) ; detail = Get ( LastErrorDetail ) ] ; JSONSetElement ( "{}" ; [ "filemaker_error" ; fmError ; JSONNumber ] ; [ "detail" ; detail ; JSONString ] ) ) ] Set Variable [ $curlOptions ; Value: "" ] 2. STATO HTTP FINALE Set Variable [ $headers ; Value: Substitute ( $responseHeaders ; [ Char ( 13 ) & Char ( 10 ) ; "¶" ] ; [ Char ( 10 ) ; "¶" ] ) ] Set Variable [ $httpStatus ; Value: 0 ] Set Variable [ $lineNumber ; Value: 1 ] Loop Exit Loop If [ $lineNumber > ValueCount ( $headers ) ] Set Variable [ $line ; Value: GetValue ( $headers ; $lineNumber ) ] If [ Exact ( Left ( $line ; 5 ) ; "HTTP/" ) ] Set Variable [ $httpStatus ; Value: GetAsNumber ( Middle ( $line ; Position ( $line ; " " ; 1 ; 1 ) + 1 ; 3 ) ) ] End If Set Variable [ $lineNumber ; Value: $lineNumber + 1 ] End Loop # Usare l'ULTIMO stato HTTP: possono precederlo blocchi informativi/proxy. # Non usare MiddleWords(): la barra in HTTP/2 altera la tokenizzazione. 3. ESITO DEL SINGOLO ELEMENTO Set Variable [ $syncOk ; Value: 0 ] If [ JSONGetElement ( $transfer ; "filemaker_error" ) = 0 and $httpStatus = 200 and JSONGetElementType ( $responseBody ; "" ) = JSONObject and JSONGetElementType ( $responseBody ; "results" ) = JSONArray and ValueCount ( JSONListKeys ( $responseBody ; "results" ) ) = 1 ] If [ JSONGetElement ( $responseBody ; "results[0].index" ) = 0 and Exact ( JSONGetElement ( $responseBody ; "results[0].filemaker_code" ) ; $centreCode ) ] Set Variable [ $outcome ; Value: JSONGetElement ( $responseBody ; "results[0].outcome" ) ] Set Variable [ $syncOk ; Value: Exact ( $outcome ; "created" ) or Exact ( $outcome ; "updated" ) or Exact ( $outcome ; "unchanged" ) ] End If End If # Soltanto $syncOk=1 consente di marcare QUESTO payload/revisione come acquisito. # Se il record sorgente è cambiato durante l'invio, la nuova revisione deve # restare in coda. Non segnare genericamente il centro come "aggiornato". # Per lotti, estendere il controllo a numero risultati, index e codice di OGNI # elemento. Conservare gli esiti separati: HTTP200 può contenere outcome:error. 4. GESTIONE ERRORI DA IMPLEMENTARE - Errore FileMaker/rete, timeout o risposta incompleta: non confermare. Riprovare stesso payload e revisione, con attese crescenti e tentativi limitati. - HTTP401/403: controllare credenziale, permessi e URL HTTPS; evitare retry ciechi. - HTTP400/413/415: correggere formato, dimensione o Content-Type. - HTTP503 globale: nessun results; riprovare dopo attesa. - HTTP200 + outcome:error: leggere error.code, error.status e error.message. Per 400 correggere i dati; per 409 risolvere il conflitto alla fonte; per 503 riprovare l'elemento senza rispedire dati nuovi con vecchia revisione. - Non incrementare automaticamente revision solo per superare un 409. - Registrare codice/revisione/indice/esito, non password, opzioni cURL, header Authorization o interi payload contenenti dati personali. - Non usare --fail: serve poter leggere il corpo degli errori HTTP. - Non usare --write-out (non documentato tra le opzioni FileMaker supportate). - Non usare -k/--insecure e non disabilitare Verify SSL Certificates. - Non seguire redirect automaticamente con credenziali. - --show-error modifica il modo in cui FileMaker rappresenta errori HTTP: controllare SEMPRE anche header, JSON e singoli outcome. - Pulire le variabili temporanee che contengono credenziali dopo l'uso. 5. LISTINO COMPLETO: NON È UN BATCH DI RECORD INDIPENDENTI POST https://staging-corsi.lakshmi.it/wp-json/lakshmi/v1/sync/price-lists # $offersJson è l'ARRAY COMPLETO costruito dai dati sorgente, non una modifica # parziale né un oggetto con chiavi numeriche. Prima sincronizzare le edizioni. # Campi di ogni offerta: # edition_code e price_tier: JSONString; # ticket_type: JSONString, "single" oppure "centre_package"; # amount_minor e included_participants: JSONNumber da campi interi; # extra_participant_minor: JSONNumber positivo oppure JSONNull. # EUR in centesimi: 12000 rappresenta 120,00 EUR; non inviare "120,00". Set Variable [ $priceBody ; Value: JSONSetElement ( "{}" ; [ "list_code" ; $persistentListCode ; JSONString ] ; [ "revision" ; $persistentListRevision ; JSONNumber ] ; [ "currency" ; "EUR" ; JSONString ] ; [ "enabled" ; $listEnabled ; JSONBoolean ] ; [ "offers" ; $offersJson ; JSONArray ] ) ] # Usare il trasporto POST della sezione 1 con $priceBody al posto di # $requestBody; catturare immediatamente gli errori e lo stato HTTP finale. # La radice NON è {"price_lists":[...]}; non cercare results[] nella risposta. # Il primo list_code accettato diventa fisso per il catalogo. Non scegliere un # nuovo codice per aggirare un conflitto. Massimo 500 offerte, corpo 256 KiB. # Confermare SOLO se: # - errore di trasporto zero e HTTP 200; # - price_list è JSONObject; # - price_list.list_code coincide esattamente con $persistentListCode; # - price_list.revision coincide con la revisione effettivamente trasmessa; # - price_list.outcome è created, updated oppure unchanged. # Controllare separatamente catalogue.outcome: # - synchronized: proiezione dei prodotti completata; # - pending: listino GIÀ acquisito; conservare lo stesso identico payload per # riprovare la proiezione, senza inventare una nuova revisione. # Una sola offerta invalida rifiuta tutto lo snapshot: HTTP 400, nessun risultato # parziale. Conflitto/revisione vecchia/list_code diverso: HTTP 409. # offers:[] ritira TUTTE le offerte. enabled:false non riattiva vecchi listini. # price-list-example.json è il listino demo completo, non un frammento da unire # al listino del cliente. Non inviarlo su un listino preesistente non concordato. 6. LETTURA EVENTI: PAGINA PIÙ VECCHIA ANCORA SENZA ACK Set Variable [ $url ; Value: "https://staging-corsi.lakshmi.it/wp-json/lakshmi/v1/events?limit=100" ] Set Variable [ $responseBody ; Value: "" ] Set Variable [ $responseHeaders ; Value: "" ] Set Variable [ $curlOptions ; Value: "--request GET --basic --user " & Quote ( $apiUser & ":" & $applicationPassword ) & " --header " & Quote ( "Accept: application/json" ) & " --dump-header $responseHeaders" & " --connect-timeout 10 --max-time 30 --show-error" ] # Eseguire Insert from URL con Verify SSL Certificates come nella sezione 1. # Catturare subito Get(LastError)/Get(LastErrorDetail), poi pulire $curlOptions # e ricavare l'ultimo stato HTTP dagli header come nella sezione 2. # Non inviare body, cursor, after oppure offset: limit=1..100 è l'unico parametro. # Accettare la pagina solo con HTTP 200, trasporto riuscito, radice JSONObject, # events JSONArray e count uguale al numero di elementi. Un 503 o JSON invalido # NON significa coda vuota. Solo count=0 ed events=[] validi indicano una pagina # vuota. Ripetere GET senza ACK restituisce di nuovo gli stessi eventi. # PSEUDOCODICE DELLA PROCEDURA LOCALE DA REALIZZARE NEL GESTIONALE: # Per ogni events[i], in ordine di sequence: # 1. Verificare event_id UUID, event_type, payload.schema_version=1. # Gli eventi attuali sono DEMO: demo=true e money_moved=false. # Non registrarli come incassi/rimborsi reali. # 2. In una transazione FileMaker, cercare event_id con vincolo UNIVOCO. # 3. Se assente, registrare fatto e record di business necessari; salvare # anche la ricevuta locale event_id -> riferimento FileMaker persistente. # Se già presente, riutilizzare lo stesso riferimento senza duplicare dati. # 4. Fare commit e verificarne il successo. In caso di errore NON fare ACK. # 5. Mettere in coda la ricevuta solo del fatto sicuramente commesso. # La forma dei passi transazionali dipende dalla versione/installazione del # cliente: questo blocco descrive la semantica richiesta, non uno script importabile. # I prezzi nel payload sono snapshot storici: non ricalcolare con il listino # corrente. I partecipanti hanno first_name/last_name; non sono account separati. # Un order.refunded contiene l'importo positivo rimborsato e paid_event_id. # participant_cancellations è sempre []; NON scegliere chi cancellare in base # all'importo. Le righe possono essere vuote per un rimborso di solo importo. 7. ACK SOLTANTO DOPO IL COMMIT FILEMAKER POST https://staging-corsi.lakshmi.it/wp-json/lakshmi/v1/events/ack # Costruire $ackBody dalle ricevute persistenti, non direttamente dal GET. # Esempio di struttura per UNA ricevuta realmente già commessa: Set Variable [ $ackBody ; Value: JSONSetElement ( "{}" ; [ "acknowledgements[0].event_id" ; $committedEventId ; JSONString ] ; [ "acknowledgements[0].filemaker_id" ; $committedFileMakerId ; JSONString ] ) ] # Riutilizzare il trasporto POST della sezione 1 con $ackBody, massimo 100 # ricevute e 256 KiB. filemaker_id è testo ASCII [A-Za-z0-9._:-], da 1 a 128 # caratteri: preservare zeri iniziali e maiuscole. Non è una credenziale. # Non inviare gli UUID inventati di ack-example.json alla coda reale. # Verificare HTTP 200 e TUTTI i results[] in ordine: # - index deve corrispondere alla ricevuta trasmessa; # - event_id e filemaker_id devono coincidere esattamente con quelli persistiti; # - outcome acknowledged oppure unchanged consente di chiudere la ricevuta locale. # HTTP 200 con outcome:error NON conferma l'elemento: # - 400: correggere oggetto/tipi/formato; # - 404: UUID sconosciuto, verificare ambiente e corrispondenza; # - 409: stesso evento già confermato con riferimento diverso, indagare; # - 503: riprovare identica ricevuta con attese crescenti e tentativi limitati. # Timeout/risposta persa: il server può avere già commesso l'ACK. Riprovare gli # stessi identificatori produce unchanged, senza doppia importazione FileMaker. # Dopo ACK dei fatti salvati, richiedere un'altra pagina GET. Gli eventi non # confermati restano nella coda: non usare un cursor e non confermare un errore # per farlo sparire. Segnalare gli eventi bloccati e risolverli alla fonte. # ACK non cancella l'evento, non annulla ordini e non abilita pagamenti reali. # Nei log mantenere solo identificatori/esiti necessari; non nomi completi, # payload personali, credenziali o header Authorization. FONTI CLARIS https://help.claris.com/en/pro-help/content/jsonsetelement.html https://help.claris.com/en/pro-help/content/jsongetelementtype.html https://help.claris.com/en/pro-help/content/curl-options.html https://help.claris.com/en/pro-help/content/insert-from-url.html https://help.claris.com/en/pro-help/content/get-lasterrordetail.html https://help.claris.com/en/pro-help/content/set-error-capture.html https://help.claris.com/en/pro-help/content/quote.html https://help.claris.com/en/pro-help/content/exact.html